WIP login workflow

This commit is contained in:
Damontecres 2025-12-20 20:32:46 -05:00
parent 1030741631
commit 2161a9de56
No known key found for this signature in database
5 changed files with 294 additions and 4 deletions

View file

@ -20,10 +20,10 @@ import org.jellyfin.sdk.model.serializer.UUIDSerializer
@Serializable
data class SeerrServer(
@PrimaryKey(autoGenerate = true)
val id: Int,
val name: String?,
val id: Int = 0,
val url: String,
val version: String?,
val name: String? = null,
val version: String? = null,
)
@Entity(

View file

@ -26,7 +26,7 @@ class SeerrApi(
fun update(
baseUrl: String,
apiKey: String,
apiKey: String?,
) {
api = SeerrApiClient(baseUrl, apiKey, okHttpClient)
}

View file

@ -0,0 +1,132 @@
package com.github.damontecres.wholphin.services
import com.github.damontecres.wholphin.api.seerr.model.AuthJellyfinPostRequest
import com.github.damontecres.wholphin.api.seerr.model.AuthLocalPostRequest
import com.github.damontecres.wholphin.data.SeerrServerDao
import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.SeerrAuthMethod
import com.github.damontecres.wholphin.data.model.SeerrServer
import com.github.damontecres.wholphin.data.model.SeerrUser
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SeerrServerRepository
@Inject
constructor(
private val seerrApi: SeerrApi,
private val seerrServerDao: SeerrServerDao,
private val serverRepository: ServerRepository,
) {
private val _currentServer = MutableStateFlow<SeerrServer?>(null)
val currentServer: StateFlow<SeerrServer?> = _currentServer
private val _currentUser = MutableStateFlow<SeerrUser?>(null)
val currentUser: StateFlow<SeerrUser?> = _currentUser
val current =
currentServer.combine(currentUser) { server, user ->
if (server != null && user != null) {
CurrentSeerr(server, user)
} else {
null
}
}
suspend fun addAndChangeServer(
url: String,
apiKey: String,
) {
var server = seerrServerDao.getServer(url)
if (server == null) {
seerrServerDao.addServer(SeerrServer(url = url))
server = seerrServerDao.getServer(url)
}
server?.server?.let { server ->
serverRepository.currentUser.value?.let { jellyfinUser ->
// TODO test api key
val user =
SeerrUser(
jellyfinRowId = jellyfinUser.rowId,
serverId = server.id,
authMethod = SeerrAuthMethod.API_KEY,
username = null,
password = null,
credential = apiKey,
)
seerrServerDao.addUser(user)
// TODO user info
seerrApi.api.usersApi.authMeGet()
seerrApi.update(server.url, apiKey)
_currentServer.update {
_currentUser.update {
user
}
server
}
}
}
}
suspend fun addAndChangeServer(
url: String,
authMethod: SeerrAuthMethod,
username: String,
password: String,
) {
var server = seerrServerDao.getServer(url)
if (server == null) {
seerrServerDao.addServer(SeerrServer(url = url))
server = seerrServerDao.getServer(url)
}
server?.server?.let { server ->
serverRepository.currentUser.value?.let { jellyfinUser ->
val response =
when (authMethod) {
SeerrAuthMethod.LOCAL -> {
seerrApi.api.authApi.authLocalPostWithHttpInfo(
AuthLocalPostRequest(
email = username,
password = password,
),
)
}
SeerrAuthMethod.JELLYFIN -> {
seerrApi.api.authApi.authJellyfinPostWithHttpInfo(
AuthJellyfinPostRequest(
username = username,
password = password,
),
)
}
SeerrAuthMethod.API_KEY -> {
throw IllegalArgumentException("Cannot authenticate with API key using this function")
}
}
response.headers["Cookies"]
// TODO get session cookie
val user =
SeerrUser(
jellyfinRowId = jellyfinUser.rowId,
serverId = server.id,
authMethod = SeerrAuthMethod.API_KEY,
username = username,
password = password,
credential = null,
)
}
}
}
}
data class CurrentSeerr(
val server: SeerrServer,
val user: SeerrUser,
)

View file

@ -0,0 +1,137 @@
package com.github.damontecres.wholphin.ui.setup.seerr
import androidx.compose.foundation.focusGroup
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.ui.components.EditTextBox
import com.github.damontecres.wholphin.ui.components.TextButton
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.tryRequestFocus
@Composable
fun AddSeerrServerContent(
onSubmit: (url: String, apiKey: String) -> Unit,
errorMessage: String?,
modifier: Modifier = Modifier,
) {
var error by remember(errorMessage) { mutableStateOf(errorMessage) }
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier =
modifier
.focusGroup()
.padding(16.dp)
.wrapContentSize(),
) {
var url by remember { mutableStateOf("") }
var apiKey by remember { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
val passwordFocusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
Text(
text = "Enter URL & API Key",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(),
)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.align(Alignment.CenterHorizontally),
) {
Text(
text = "URL",
modifier = Modifier.padding(end = 8.dp),
)
EditTextBox(
value = url,
onValueChange = {
error = null
url = it
},
keyboardOptions =
KeyboardOptions(
capitalization = KeyboardCapitalization.None,
autoCorrectEnabled = false,
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Next,
),
keyboardActions =
KeyboardActions(
onNext = {
passwordFocusRequester.tryRequestFocus()
},
),
isInputValid = { true },
modifier = Modifier.focusRequester(focusRequester),
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.align(Alignment.CenterHorizontally),
) {
Text(
text = "API Key",
modifier = Modifier.padding(end = 8.dp),
)
EditTextBox(
value = apiKey,
onValueChange = {
error = null
apiKey = it
},
keyboardOptions =
KeyboardOptions(
capitalization = KeyboardCapitalization.None,
autoCorrectEnabled = false,
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Go,
),
keyboardActions =
KeyboardActions(
onGo = { onSubmit.invoke(url, apiKey) },
),
isInputValid = { true },
modifier = Modifier.focusRequester(passwordFocusRequester),
)
}
error?.let {
Text(
text = it,
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.titleLarge,
)
}
TextButton(
stringRes = R.string.submit,
onClick = { onSubmit.invoke(url, apiKey) },
enabled = error.isNullOrBlank() && url.isNotNullOrBlank() && apiKey.isNotNullOrBlank(),
modifier = Modifier.align(Alignment.CenterHorizontally),
)
}
}

View file

@ -0,0 +1,21 @@
package com.github.damontecres.wholphin.ui.setup.seerr
import androidx.lifecycle.ViewModel
import com.github.damontecres.wholphin.services.SeerrServerRepository
import com.github.damontecres.wholphin.services.SeerrService
import dagger.hilt.android.lifecycle.HiltViewModel
import jakarta.inject.Inject
@HiltViewModel
class SwitchSeerrViewModel
@Inject
constructor(
private val seerrServerRepository: SeerrServerRepository,
private val seerrService: SeerrService,
) : ViewModel() {
fun submitServer(
url: String,
apiKey: String,
) {
}
}