refactor(auth): migrate Auth module from feature to core package
- Reorganized `auth-feature` into `core/auth` to improve architecture and modularity. - Removed unused PKCE and OAuth callback utilities (`AuthCallbackParams`, `OAuthPkceService`). - Updated imports and adjusted build scripts to reflect new module structure. - Refactored `LoginScreen` and `PingScreen` to include `onBack` functionality in top bars for improved navigation. - Corrected sync endpoint in `PingSyncService`.
This commit is contained in:
@@ -80,8 +80,8 @@ kotlin {
|
||||
implementation(projects.frontend.core.navigation)
|
||||
implementation(projects.frontend.core.network)
|
||||
implementation(projects.frontend.core.sync)
|
||||
implementation(project(":frontend:core:local-db"))
|
||||
implementation(projects.frontend.features.authFeature)
|
||||
implementation(projects.frontend.core.localDb)
|
||||
implementation(projects.frontend.core.auth)
|
||||
implementation(projects.frontend.features.pingFeature)
|
||||
|
||||
// DI (Koin) needed to call initKoin { modules(...) }
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import at.mocode.clients.shared.navigation.AppScreen
|
||||
import at.mocode.clients.authfeature.AuthTokenManager
|
||||
import at.mocode.frontend.core.auth.data.AuthTokenManager
|
||||
import at.mocode.frontend.core.auth.data.AuthApiClient
|
||||
import at.mocode.frontend.core.auth.presentation.LoginScreen
|
||||
import at.mocode.frontend.core.auth.presentation.LoginViewModel
|
||||
import at.mocode.ping.feature.presentation.PingScreen
|
||||
import at.mocode.ping.feature.presentation.PingViewModel
|
||||
import at.mocode.shared.core.AppConstants
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import at.mocode.frontend.core.designsystem.components.AppFooter
|
||||
import kotlinx.coroutines.launch
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import at.mocode.clients.authfeature.AuthApiClient
|
||||
import at.mocode.clients.authfeature.LoginViewModel
|
||||
import at.mocode.clients.authfeature.oauth.OAuthPkceService
|
||||
import at.mocode.clients.authfeature.oauth.AuthCallbackParams
|
||||
import at.mocode.clients.authfeature.oauth.CallbackParams
|
||||
import org.koin.compose.koinInject
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@@ -33,29 +32,10 @@ fun MainApp() {
|
||||
|
||||
// Resolve AuthTokenManager from Koin
|
||||
val authTokenManager = koinInject<AuthTokenManager>()
|
||||
val authApiClient = koinInject<AuthApiClient>()
|
||||
|
||||
// Delta-Sync blueprint: resolve the Ping feature view model via Koin.
|
||||
val pingViewModel: PingViewModel = koinViewModel()
|
||||
// scope removed (unused)
|
||||
|
||||
// Handle PKCE callback on an app load (web)
|
||||
LaunchedEffect(Unit) {
|
||||
val callback: CallbackParams? = AuthCallbackParams.parse()
|
||||
if (callback != null) {
|
||||
val code = callback.code
|
||||
val state = callback.state
|
||||
val pkce = OAuthPkceService.current()
|
||||
if (pkce != null && pkce.state == state) {
|
||||
val res = authApiClient.exchangeAuthorizationCode(code, pkce.codeVerifier, AppConstants.webRedirectUri())
|
||||
val token = res.token
|
||||
if (res.success && token != null) {
|
||||
authTokenManager.setToken(token)
|
||||
OAuthPkceService.clear()
|
||||
currentScreen = AppScreen.Profile
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val loginViewModel: LoginViewModel = koinViewModel()
|
||||
|
||||
when (currentScreen) {
|
||||
is AppScreen.Landing -> LandingScreen(
|
||||
@@ -65,18 +45,21 @@ fun MainApp() {
|
||||
is AppScreen.Home -> WelcomeScreen(
|
||||
authTokenManager = authTokenManager,
|
||||
onOpenPing = { currentScreen = AppScreen.Ping },
|
||||
onOpenLogin = {
|
||||
// Fallback to the local LoginScreen (Password Grant) if PKCE cannot be started
|
||||
currentScreen = AppScreen.Login
|
||||
},
|
||||
onOpenLogin = { currentScreen = AppScreen.Login },
|
||||
onOpenProfile = { currentScreen = AppScreen.Profile }
|
||||
)
|
||||
|
||||
is AppScreen.Login -> LoginScreen(
|
||||
onLoginSuccess = { currentScreen = AppScreen.Profile }
|
||||
viewModel = loginViewModel,
|
||||
onLoginSuccess = { currentScreen = AppScreen.Profile },
|
||||
onBack = { currentScreen = AppScreen.Home }
|
||||
)
|
||||
|
||||
is AppScreen.Ping -> PingScreen(
|
||||
viewModel = pingViewModel,
|
||||
onBack = { currentScreen = AppScreen.Home } // Navigate back to Home
|
||||
)
|
||||
|
||||
is AppScreen.Ping -> PingScreen(viewModel = pingViewModel)
|
||||
is AppScreen.Profile -> AuthStatusScreen(
|
||||
authTokenManager = authTokenManager,
|
||||
onBackToHome = { currentScreen = AppScreen.Home }
|
||||
@@ -93,8 +76,13 @@ private fun LandingScreen(
|
||||
onPrimaryCta: () -> Unit,
|
||||
onSecondary: () -> Unit
|
||||
) {
|
||||
// Minimal Landing-Layout: Hero → Manifest → Features → Footer
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(scrollState)
|
||||
) {
|
||||
// Hero
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -102,7 +90,6 @@ private fun LandingScreen(
|
||||
.padding(horizontal = 24.dp, vertical = 40.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
// Wortmarke (Platzhalter)
|
||||
Text(
|
||||
text = "Equest‑Events",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
@@ -179,7 +166,7 @@ private fun LandingScreen(
|
||||
}
|
||||
|
||||
// Footer
|
||||
at.mocode.clients.shared.commonui.components.AppFooter()
|
||||
AppFooter()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,12 +194,12 @@ private fun WelcomeScreen(
|
||||
onOpenProfile: () -> Unit
|
||||
) {
|
||||
val authState by authTokenManager.authState.collectAsState()
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(scrollState)
|
||||
.padding(24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
@@ -239,41 +226,11 @@ private fun WelcomeScreen(
|
||||
Button(onClick = onOpenPing, modifier = Modifier.weight(1f)) { Text("Ping-Service") }
|
||||
if (!authState.isAuthenticated) {
|
||||
Button(
|
||||
onClick = {
|
||||
// Try PKCE login (Authorization Code Flow w/ PKCE)
|
||||
scope.launch {
|
||||
try {
|
||||
val pkce = OAuthPkceService.startAuth()
|
||||
val url = OAuthPkceService.buildAuthorizeUrl(pkce, AppConstants.webRedirectUri())
|
||||
uriHandler.openUri(url)
|
||||
} catch (_: Throwable) {
|
||||
// Fallback: open the local Login screen (Password Grant)
|
||||
onOpenLogin()
|
||||
}
|
||||
}
|
||||
},
|
||||
onClick = onOpenLogin,
|
||||
modifier = Modifier.weight(1f)
|
||||
) { Text("Login") }
|
||||
}
|
||||
}
|
||||
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
OutlinedButton(
|
||||
onClick = { uriHandler.openUri(AppConstants.registerUrl()) },
|
||||
modifier = Modifier.weight(1f)
|
||||
) { Text("Registrieren (Keycloak)") }
|
||||
|
||||
OutlinedButton(
|
||||
onClick = { uriHandler.openUri(AppConstants.loginUrl()) },
|
||||
modifier = Modifier.weight(1f)
|
||||
) { Text("Keycloak Login-Seite") }
|
||||
}
|
||||
|
||||
// Desktop Download Link
|
||||
OutlinedButton(
|
||||
onClick = { uriHandler.openUri(AppConstants.desktopDownloadUrl()) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) { Text("Desktop-App herunterladen") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,6 +256,9 @@ private fun AuthStatusScreen(
|
||||
authTokenManager.clearToken()
|
||||
onBackToHome()
|
||||
}) { Text("Abmelden") }
|
||||
|
||||
Spacer(Modifier.height(8.dp))
|
||||
OutlinedButton(onClick = onBackToHome) { Text("Zurück zur Startseite") }
|
||||
} else {
|
||||
Text("Nicht angemeldet.")
|
||||
Spacer(Modifier.height(8.dp))
|
||||
@@ -308,64 +268,3 @@ private fun AuthStatusScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoginScreen(
|
||||
onLoginSuccess: () -> Unit
|
||||
) {
|
||||
val viewModel = koinViewModel<LoginViewModel>()
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
|
||||
LaunchedEffect(uiState.isAuthenticated) {
|
||||
if (uiState.isAuthenticated) {
|
||||
onLoginSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Text("Anmeldung", style = MaterialTheme.typography.headlineMedium)
|
||||
|
||||
OutlinedTextField(
|
||||
value = uiState.username,
|
||||
onValueChange = { viewModel.updateUsername(it) },
|
||||
label = { Text("Benutzername") },
|
||||
singleLine = true,
|
||||
enabled = !uiState.isLoading,
|
||||
isError = uiState.usernameError != null,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
if (uiState.usernameError != null) {
|
||||
Text(uiState.usernameError!!, color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
value = uiState.password,
|
||||
onValueChange = { viewModel.updatePassword(it) },
|
||||
label = { Text("Passwort") },
|
||||
singleLine = true,
|
||||
enabled = !uiState.isLoading,
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
isError = uiState.passwordError != null,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
if (uiState.passwordError != null) {
|
||||
Text(uiState.passwordError!!, color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
|
||||
if (uiState.errorMessage != null) {
|
||||
Text(uiState.errorMessage!!, color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Button(
|
||||
onClick = { viewModel.login() },
|
||||
enabled = uiState.canLogin
|
||||
) { Text(if (uiState.isLoading) "Bitte warten…" else "Login") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package navigation
|
||||
|
||||
import at.mocode.clients.authfeature.AuthTokenManager
|
||||
import at.mocode.frontend.core.auth.data.AuthTokenManager
|
||||
import at.mocode.frontend.core.domain.models.User
|
||||
import at.mocode.frontend.core.navigation.CurrentUserProvider
|
||||
import at.mocode.frontend.core.navigation.DeepLinkHandler
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.window.ComposeViewport
|
||||
import at.mocode.clients.authfeature.di.authFeatureModule
|
||||
import at.mocode.frontend.core.auth.di.authModule
|
||||
import at.mocode.frontend.core.localdb.AppDatabase
|
||||
import at.mocode.frontend.core.localdb.DatabaseProvider
|
||||
import at.mocode.frontend.core.localdb.localDbModule
|
||||
@@ -27,8 +27,8 @@ fun main() {
|
||||
// Initialize DI (Koin) with shared modules + network + local DB modules
|
||||
try {
|
||||
// Updated: Only load the consolidated pingFeatureModule from at.mocode.ping.feature.di
|
||||
initKoin { modules(networkModule, localDbModule, syncModule, pingFeatureModule, authFeatureModule, navigationModule) }
|
||||
console.log("[WebApp] Koin initialized with networkModule + localDbModule + authFeatureModule + navigationModule + pingFeatureModule")
|
||||
initKoin { modules(networkModule, localDbModule, syncModule, pingFeatureModule, authModule, navigationModule) }
|
||||
console.log("[WebApp] Koin initialized with networkModule + localDbModule + authModule + navigationModule + pingFeatureModule")
|
||||
} catch (e: dynamic) {
|
||||
console.warn("[WebApp] Koin initialization warning:", e)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import androidx.compose.ui.window.WindowState
|
||||
import androidx.compose.ui.unit.dp
|
||||
import at.mocode.shared.di.initKoin
|
||||
import at.mocode.frontend.core.network.networkModule
|
||||
import at.mocode.clients.authfeature.di.authFeatureModule
|
||||
import at.mocode.frontend.core.auth.di.authModule
|
||||
import at.mocode.frontend.core.sync.di.syncModule
|
||||
import at.mocode.ping.feature.di.pingFeatureModule
|
||||
import at.mocode.frontend.core.localdb.AppDatabase
|
||||
@@ -19,8 +19,8 @@ fun main() = application {
|
||||
// Initialize DI (Koin) with shared modules + network module
|
||||
try {
|
||||
// Updated: Only load the consolidated pingFeatureModule from at.mocode.ping.feature.di
|
||||
initKoin { modules(networkModule, syncModule, pingFeatureModule, authFeatureModule, navigationModule, localDbModule) }
|
||||
println("[DesktopApp] Koin initialized with networkModule + authFeatureModule + navigationModule + pingFeatureModule + localDbModule")
|
||||
initKoin { modules(networkModule, syncModule, pingFeatureModule, authModule, navigationModule, localDbModule) }
|
||||
println("[DesktopApp] Koin initialized with networkModule + authModule + navigationModule + pingFeatureModule + localDbModule")
|
||||
} catch (e: Exception) {
|
||||
println("[DesktopApp] Koin initialization warning: ${e.message}")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user