mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 16:11:20 +02:00
Add integration with Jellyseerr/Seerr (#558)
## Description This PR add the ability to integrate with a [Jellyseerr/Seerr server](https://github.com/seerr-team/seerr). ### Features - Login to the Seerr server using API Key, Jellyfin credentials, or local Seerr credentials - Separate Seerr logins per user profile - Search Seerr from the search page - Discover media from Seerr on the nav drawer `Discover` page - Also from movie, series, or person pages - Seamless integration with existing media, i.e. selecting media found from Seerr that already exists on the Jellyfin server, will go directly to the regular media page - Mostly consistent UI between Jellyfin media & Seerr media - Submit requests to Seerr - Cancel submitted requests ### Screenshots  ### Related issues Closes #54 ## Acknowledgements The `app/src/main/seerr/seerr-api.yml` file is copied & modified from https://github.com/seerr-team/seerr/blob/develop/seerr-api.yml. I hope to try to upstream some of the changes in the future.
This commit is contained in:
parent
f4e1b0e171
commit
4c7c465c67
68 changed files with 13369 additions and 137 deletions
|
|
@ -15,6 +15,8 @@ import com.github.damontecres.wholphin.data.model.JellyfinUser
|
|||
import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
|
||||
import com.github.damontecres.wholphin.data.model.NavDrawerPinnedItem
|
||||
import com.github.damontecres.wholphin.data.model.PlaybackLanguageChoice
|
||||
import com.github.damontecres.wholphin.data.model.SeerrServer
|
||||
import com.github.damontecres.wholphin.data.model.SeerrUser
|
||||
import com.github.damontecres.wholphin.ui.components.ViewOptions
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||
|
|
@ -32,8 +34,10 @@ import java.util.UUID
|
|||
LibraryDisplayInfo::class,
|
||||
PlaybackLanguageChoice::class,
|
||||
ItemTrackModification::class,
|
||||
SeerrServer::class,
|
||||
SeerrUser::class,
|
||||
],
|
||||
version = 12,
|
||||
version = 20,
|
||||
exportSchema = true,
|
||||
autoMigrations = [
|
||||
AutoMigration(3, 4),
|
||||
|
|
@ -45,6 +49,7 @@ import java.util.UUID
|
|||
AutoMigration(9, 10),
|
||||
AutoMigration(10, 11),
|
||||
AutoMigration(11, 12),
|
||||
AutoMigration(12, 20),
|
||||
],
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
|
|
@ -58,6 +63,8 @@ abstract class AppDatabase : RoomDatabase() {
|
|||
abstract fun libraryDisplayInfoDao(): LibraryDisplayInfoDao
|
||||
|
||||
abstract fun playbackLanguageChoiceDao(): PlaybackLanguageChoiceDao
|
||||
|
||||
abstract fun seerrServerDao(): SeerrServerDao
|
||||
}
|
||||
|
||||
class Converters {
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ import android.content.Context
|
|||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.NavDrawerPinnedItem
|
||||
import com.github.damontecres.wholphin.data.model.NavPinType
|
||||
import com.github.damontecres.wholphin.services.SeerrServerRepository
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
|
||||
import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem
|
||||
import com.github.damontecres.wholphin.util.supportedCollectionTypes
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.first
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.liveTvApi
|
||||
import org.jellyfin.sdk.api.client.extensions.userViewsApi
|
||||
|
|
@ -24,6 +26,7 @@ class NavDrawerItemRepository
|
|||
private val api: ApiClient,
|
||||
private val serverRepository: ServerRepository,
|
||||
private val serverPreferencesDao: ServerPreferencesDao,
|
||||
private val seerrServerRepository: SeerrServerRepository,
|
||||
) {
|
||||
suspend fun getNavDrawerItems(): List<NavDrawerItem> {
|
||||
val user = serverRepository.currentUser.value
|
||||
|
|
@ -46,7 +49,13 @@ class NavDrawerItemRepository
|
|||
setOf()
|
||||
}
|
||||
|
||||
val builtins = listOf(NavDrawerItem.Favorites)
|
||||
val builtins =
|
||||
if (seerrServerRepository.active.first()) {
|
||||
listOf(NavDrawerItem.Favorites, NavDrawerItem.Discover)
|
||||
} else {
|
||||
listOf(NavDrawerItem.Favorites)
|
||||
}
|
||||
|
||||
val libraries =
|
||||
userViews
|
||||
.filter { it.collectionType in supportedCollectionTypes || it.id in recordingFolders }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
package com.github.damontecres.wholphin.data
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import com.github.damontecres.wholphin.data.model.SeerrServer
|
||||
import com.github.damontecres.wholphin.data.model.SeerrServerUsers
|
||||
import com.github.damontecres.wholphin.data.model.SeerrUser
|
||||
|
||||
@Dao
|
||||
interface SeerrServerDao {
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun addServer(server: SeerrServer): Long
|
||||
|
||||
@Update
|
||||
suspend fun updateServer(server: SeerrServer): Int
|
||||
|
||||
@Transaction
|
||||
suspend fun addOrUpdateServer(server: SeerrServer) {
|
||||
val result = addServer(server)
|
||||
if (result == -1L) {
|
||||
updateServer(server)
|
||||
}
|
||||
}
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun addUser(user: SeerrUser): Long
|
||||
|
||||
suspend fun updateUser(user: SeerrUser) = addUser(user)
|
||||
|
||||
@Query("SELECT * FROM seerr_users WHERE serverId = :serverId AND jellyfinUserRowId = :jellyfinUserRowId")
|
||||
suspend fun getUser(
|
||||
serverId: Int,
|
||||
jellyfinUserRowId: Int,
|
||||
): SeerrUser?
|
||||
|
||||
@Query("SELECT * FROM seerr_users WHERE jellyfinUserRowId = :jellyfinUserRowId")
|
||||
suspend fun getUsersByJellyfinUser(jellyfinUserRowId: Int): List<SeerrUser>
|
||||
|
||||
@Query("DELETE FROM seerr_servers WHERE id = :serverId")
|
||||
suspend fun deleteServer(serverId: Int)
|
||||
|
||||
@Query("DELETE FROM seerr_users WHERE serverId = :serverId AND jellyfinUserRowId = :jellyfinUserRowId")
|
||||
suspend fun deleteUser(
|
||||
serverId: Int,
|
||||
jellyfinUserRowId: Int,
|
||||
)
|
||||
|
||||
suspend fun deleteUser(user: SeerrUser) = deleteUser(user.serverId, user.jellyfinUserRowId)
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM seerr_servers")
|
||||
suspend fun getServers(): List<SeerrServerUsers>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM seerr_servers WHERE id = :serverId")
|
||||
suspend fun getServer(serverId: Int): SeerrServerUsers?
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM seerr_servers WHERE url = :url")
|
||||
suspend fun getServer(url: String): SeerrServerUsers?
|
||||
}
|
||||
|
|
@ -23,7 +23,9 @@ data class BaseItem(
|
|||
val data: BaseItemDto,
|
||||
val useSeriesForPrimary: Boolean,
|
||||
) : CardGridItem {
|
||||
override val id get() = data.id
|
||||
val id get() = data.id
|
||||
|
||||
override val gridId get() = id.toString()
|
||||
|
||||
override val playable: Boolean
|
||||
get() = type.playable
|
||||
|
|
@ -88,23 +90,21 @@ data class BaseItem(
|
|||
Destination.SeriesOverview(
|
||||
data.seriesId!!,
|
||||
BaseItemKind.SERIES,
|
||||
this,
|
||||
SeasonEpisodeIds(seasonId, data.parentIndexNumber, id, indexNumber),
|
||||
)
|
||||
} ?: Destination.MediaItem(id, type, this)
|
||||
} ?: Destination.MediaItem(this)
|
||||
}
|
||||
|
||||
BaseItemKind.SEASON -> {
|
||||
Destination.SeriesOverview(
|
||||
data.seriesId!!,
|
||||
BaseItemKind.SERIES,
|
||||
this,
|
||||
SeasonEpisodeIds(id, indexNumber, null, null),
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
Destination.MediaItem(id, type, this)
|
||||
Destination.MediaItem(this)
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -0,0 +1,226 @@
|
|||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package com.github.damontecres.wholphin.data.model
|
||||
|
||||
import com.github.damontecres.wholphin.api.seerr.model.CreditCast
|
||||
import com.github.damontecres.wholphin.api.seerr.model.CreditCrew
|
||||
import com.github.damontecres.wholphin.api.seerr.model.MovieDetails
|
||||
import com.github.damontecres.wholphin.api.seerr.model.MovieMovieIdRatingsGet200Response
|
||||
import com.github.damontecres.wholphin.api.seerr.model.MovieResult
|
||||
import com.github.damontecres.wholphin.api.seerr.model.TvDetails
|
||||
import com.github.damontecres.wholphin.api.seerr.model.TvResult
|
||||
import com.github.damontecres.wholphin.api.seerr.model.TvTvIdRatingsGet200Response
|
||||
import com.github.damontecres.wholphin.services.SeerrSearchResult
|
||||
import com.github.damontecres.wholphin.ui.detail.CardGridItem
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.toLocalDate
|
||||
import com.github.damontecres.wholphin.util.LocalDateSerializer
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import java.time.LocalDate
|
||||
import java.util.UUID
|
||||
|
||||
@Serializable
|
||||
enum class SeerrItemType(
|
||||
val baseItemKind: BaseItemKind?,
|
||||
) {
|
||||
@SerialName("movie")
|
||||
MOVIE(BaseItemKind.MOVIE),
|
||||
|
||||
@SerialName("tv")
|
||||
TV(BaseItemKind.SERIES),
|
||||
|
||||
@SerialName("person")
|
||||
PERSON(BaseItemKind.PERSON),
|
||||
|
||||
@SerialName("unknown")
|
||||
UNKNOWN(null),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun fromString(
|
||||
str: String?,
|
||||
fallback: SeerrItemType = UNKNOWN,
|
||||
) = when (str) {
|
||||
"movie" -> MOVIE
|
||||
"tv" -> TV
|
||||
"person" -> PERSON
|
||||
else -> fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class SeerrAvailability(
|
||||
val status: Int,
|
||||
) {
|
||||
UNKNOWN(1),
|
||||
PENDING(2),
|
||||
PROCESSING(3),
|
||||
PARTIALLY_AVAILABLE(4),
|
||||
AVAILABLE(5),
|
||||
DELETED(6),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun from(status: Int?) = entries.firstOrNull { it.status == status }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An item provided by a discovery service (ie Seerr). It may exist on the JF server as well.
|
||||
*/
|
||||
@Serializable
|
||||
data class DiscoverItem(
|
||||
val id: Int,
|
||||
val type: SeerrItemType,
|
||||
val title: String?,
|
||||
val subtitle: String?,
|
||||
val overview: String?,
|
||||
val availability: SeerrAvailability,
|
||||
@Serializable(LocalDateSerializer::class) val releaseDate: LocalDate?,
|
||||
val posterPath: String?,
|
||||
val backdropPath: String?,
|
||||
val jellyfinItemId: UUID?,
|
||||
) : CardGridItem {
|
||||
override val gridId: String get() = id.toString()
|
||||
override val playable: Boolean = false
|
||||
override val sortName: String get() = title ?: ""
|
||||
|
||||
val backDropUrl: String? get() = backdropPath?.let { "https://image.tmdb.org/t/p/w1920_and_h800_multi_faces$it" }
|
||||
val posterUrl: String? get() = posterPath?.let { "https://image.tmdb.org/t/p/w500$it" }
|
||||
|
||||
val destination: Destination
|
||||
get() {
|
||||
val jfType =
|
||||
when (type) {
|
||||
SeerrItemType.MOVIE -> BaseItemKind.MOVIE
|
||||
SeerrItemType.TV -> BaseItemKind.SERIES
|
||||
SeerrItemType.PERSON -> BaseItemKind.PERSON
|
||||
SeerrItemType.UNKNOWN -> null
|
||||
}
|
||||
return if (jellyfinItemId != null && jfType != null) {
|
||||
Destination.MediaItem(
|
||||
itemId = jellyfinItemId,
|
||||
type = jfType,
|
||||
)
|
||||
} else {
|
||||
Destination.DiscoveredItem(this)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(movie: MovieResult) : this(
|
||||
id = movie.id,
|
||||
type = SeerrItemType.MOVIE,
|
||||
title = movie.title,
|
||||
subtitle = null,
|
||||
overview = movie.overview,
|
||||
availability = SeerrAvailability.from(movie.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN,
|
||||
releaseDate = toLocalDate(movie.releaseDate),
|
||||
posterPath = movie.posterPath,
|
||||
backdropPath = movie.backdropPath,
|
||||
jellyfinItemId = movie.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||
)
|
||||
|
||||
constructor(movie: MovieDetails) : this(
|
||||
id = movie.id ?: -1,
|
||||
type = SeerrItemType.MOVIE,
|
||||
title = movie.title,
|
||||
subtitle = null,
|
||||
overview = movie.overview,
|
||||
availability = SeerrAvailability.from(movie.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN,
|
||||
releaseDate = toLocalDate(movie.releaseDate),
|
||||
posterPath = movie.posterPath,
|
||||
backdropPath = movie.backdropPath,
|
||||
jellyfinItemId = movie.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||
)
|
||||
|
||||
constructor(tv: TvResult) : this(
|
||||
id = tv.id!!,
|
||||
type = SeerrItemType.TV,
|
||||
title = tv.name,
|
||||
subtitle = null,
|
||||
overview = tv.overview,
|
||||
availability = SeerrAvailability.from(tv.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN,
|
||||
releaseDate = toLocalDate(tv.firstAirDate),
|
||||
posterPath = tv.posterPath,
|
||||
backdropPath = tv.backdropPath,
|
||||
jellyfinItemId = tv.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||
)
|
||||
|
||||
constructor(tv: TvDetails) : this(
|
||||
id = tv.id!!,
|
||||
type = SeerrItemType.TV,
|
||||
title = tv.name,
|
||||
subtitle = null,
|
||||
overview = tv.overview,
|
||||
availability = SeerrAvailability.from(tv.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN,
|
||||
releaseDate = toLocalDate(tv.firstAirDate),
|
||||
posterPath = tv.posterPath,
|
||||
backdropPath = tv.backdropPath,
|
||||
jellyfinItemId = tv.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||
)
|
||||
|
||||
constructor(search: SeerrSearchResult) : this(
|
||||
id = search.id,
|
||||
type = SeerrItemType.fromString(search.mediaType),
|
||||
title = search.title ?: search.name,
|
||||
subtitle = null,
|
||||
overview = search.overview,
|
||||
availability =
|
||||
SeerrAvailability.from(search.mediaInfo?.status)
|
||||
?: SeerrAvailability.UNKNOWN,
|
||||
releaseDate = toLocalDate(search.releaseDate ?: search.firstAirDate),
|
||||
posterPath = search.posterPath,
|
||||
backdropPath = search.backdropPath,
|
||||
jellyfinItemId = search.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||
)
|
||||
|
||||
constructor(credit: CreditCast) : this(
|
||||
id = credit.id!!,
|
||||
type = SeerrItemType.fromString(credit.mediaType, SeerrItemType.PERSON),
|
||||
title = credit.name ?: credit.title,
|
||||
subtitle = null,
|
||||
overview = credit.overview,
|
||||
availability =
|
||||
SeerrAvailability.from(credit.mediaInfo?.status)
|
||||
?: SeerrAvailability.UNKNOWN,
|
||||
releaseDate = toLocalDate(credit.firstAirDate),
|
||||
posterPath = credit.posterPath ?: credit.profilePath,
|
||||
backdropPath = credit.backdropPath,
|
||||
jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||
)
|
||||
|
||||
constructor(credit: CreditCrew) : this(
|
||||
id = credit.id!!,
|
||||
type = SeerrItemType.fromString(credit.mediaType, SeerrItemType.PERSON),
|
||||
title = credit.name ?: credit.title,
|
||||
subtitle = null,
|
||||
overview = credit.overview,
|
||||
availability =
|
||||
SeerrAvailability.from(credit.mediaInfo?.status)
|
||||
?: SeerrAvailability.UNKNOWN,
|
||||
releaseDate = toLocalDate(credit.firstAirDate),
|
||||
posterPath = credit.posterPath ?: credit.profilePath,
|
||||
backdropPath = credit.backdropPath,
|
||||
jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||
)
|
||||
}
|
||||
|
||||
data class DiscoverRating(
|
||||
val criticRating: Int?,
|
||||
val audienceRating: Float?,
|
||||
) {
|
||||
constructor(rating: MovieMovieIdRatingsGet200Response) : this(
|
||||
criticRating = rating.criticsScore,
|
||||
audienceRating = rating.audienceScore?.div(10f),
|
||||
)
|
||||
constructor(rating: TvTvIdRatingsGet200Response) : this(
|
||||
criticRating = rating.criticsScore,
|
||||
audienceRating = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.github.damontecres.wholphin.data.model
|
||||
|
||||
import com.github.damontecres.wholphin.services.SeerrUserConfig
|
||||
|
||||
enum class SeerrPermission(
|
||||
private val flag: Int,
|
||||
) {
|
||||
// Source: https://github.com/seerr-team/seerr/blob/develop/server/lib/permissions.ts
|
||||
NONE(0),
|
||||
ADMIN(2),
|
||||
MANAGE_SETTINGS(4),
|
||||
MANAGE_USERS(8),
|
||||
MANAGE_REQUESTS(16),
|
||||
REQUEST(32),
|
||||
VOTE(64),
|
||||
AUTO_APPROVE(128),
|
||||
AUTO_APPROVE_MOVIE(256),
|
||||
AUTO_APPROVE_TV(512),
|
||||
REQUEST_4K(1024),
|
||||
REQUEST_4K_MOVIE(2048),
|
||||
REQUEST_4K_TV(4096),
|
||||
REQUEST_ADVANCED(8192),
|
||||
REQUEST_VIEW(16384),
|
||||
AUTO_APPROVE_4K(32768),
|
||||
AUTO_APPROVE_4K_MOVIE(65536),
|
||||
AUTO_APPROVE_4K_TV(131072),
|
||||
REQUEST_MOVIE(262144),
|
||||
REQUEST_TV(524288),
|
||||
MANAGE_ISSUES(1048576),
|
||||
VIEW_ISSUES(2097152),
|
||||
CREATE_ISSUES(4194304),
|
||||
AUTO_REQUEST(8388608),
|
||||
AUTO_REQUEST_MOVIE(16777216),
|
||||
AUTO_REQUEST_TV(33554432),
|
||||
RECENT_VIEW(67108864),
|
||||
WATCHLIST_VIEW(134217728),
|
||||
MANAGE_BLACKLIST(268435456),
|
||||
VIEW_BLACKLIST(1073741824),
|
||||
;
|
||||
|
||||
internal fun hasPermission(permissions: Int) = flag.and(permissions) == flag
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the user has the given permissions (or is an admin)
|
||||
*/
|
||||
fun SeerrUserConfig?.hasPermission(permission: SeerrPermission): Boolean {
|
||||
return permission.hasPermission(this?.permissions ?: return false) || SeerrPermission.ADMIN.hasPermission(permissions)
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package com.github.damontecres.wholphin.data.model
|
||||
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.Relation
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
||||
|
||||
@Entity(
|
||||
tableName = "seerr_servers",
|
||||
indices = [Index("url", unique = true)],
|
||||
)
|
||||
@Serializable
|
||||
data class SeerrServer(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
val id: Int = 0,
|
||||
val url: String,
|
||||
val name: String? = null,
|
||||
val version: String? = null,
|
||||
)
|
||||
|
||||
@Entity(
|
||||
tableName = "seerr_users",
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = SeerrServer::class,
|
||||
parentColumns = arrayOf("id"),
|
||||
childColumns = arrayOf("serverId"),
|
||||
onDelete = ForeignKey.CASCADE,
|
||||
),
|
||||
ForeignKey(
|
||||
entity = JellyfinUser::class,
|
||||
parentColumns = arrayOf("rowId"),
|
||||
childColumns = arrayOf("jellyfinUserRowId"),
|
||||
onDelete = ForeignKey.CASCADE,
|
||||
),
|
||||
],
|
||||
primaryKeys = ["jellyfinUserRowId", "serverId"],
|
||||
)
|
||||
data class SeerrUser(
|
||||
val jellyfinUserRowId: Int,
|
||||
val serverId: Int,
|
||||
val authMethod: SeerrAuthMethod,
|
||||
val username: String?,
|
||||
val password: String?,
|
||||
val credential: String?,
|
||||
) {
|
||||
override fun toString(): String =
|
||||
"SeerrUser(jellyfinUserRowId=$jellyfinUserRowId, serverId=$serverId, authMethod=$authMethod, username=$username, password?=${password.isNotNullOrBlank()}, credential?=${credential.isNotNullOrBlank()})"
|
||||
}
|
||||
|
||||
enum class SeerrAuthMethod {
|
||||
LOCAL,
|
||||
JELLYFIN,
|
||||
API_KEY,
|
||||
}
|
||||
|
||||
data class SeerrServerUsers(
|
||||
@Embedded val server: SeerrServer,
|
||||
@Relation(
|
||||
parentColumn = "id",
|
||||
entityColumn = "serverId",
|
||||
)
|
||||
val users: List<SeerrUser>,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue