feat: Shoow validation errors properly

This commit is contained in:
Peter Vacho 2024-12-23 18:56:52 +01:00
parent 724d3486a9
commit ddf4468a7f
Signed by: school
GPG key ID: 8CFC3837052871B4
4 changed files with 108 additions and 6 deletions

View file

@ -3,12 +3,21 @@ package com.p_vacho.neat_calendar
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.p_vacho.neat_calendar.auth.RegisterResult
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class RegisterActivity : AppCompatActivity() {
private val authRepository by lazy { (application as MyApplication).authRepository }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
@ -33,7 +42,51 @@ class RegisterActivity : AppCompatActivity() {
}
private fun handleRegisterSubmit() {
TODO("Not yet implemented")
val usernameInput = findViewById<EditText>(R.id.usernameInput)
val passwordInput = findViewById<EditText>(R.id.passwordInput)
val emailInput = findViewById<EditText>(R.id.emailInput)
val username = usernameInput.text.toString().trim()
val password = passwordInput.text.toString().trim()
val email = emailInput.text.toString().trim()
if (username.isEmpty() || password.isEmpty() || email.isEmpty()) {
Toast.makeText(this, "Please fill in all fields", Toast.LENGTH_SHORT).show()
} else {
performRegister(username, password, email)
}
}
private fun performRegister(username: String, password: String, email: String) {
CoroutineScope(Dispatchers.Main).launch {
val result = authRepository.register(username, password, email)
when (result) {
is RegisterResult.Success -> {
Toast.makeText(this@RegisterActivity, "User registered, you may now log in", Toast.LENGTH_SHORT).show()
navigateToLoginActivity()
}
is RegisterResult.ValidationError -> {
val errorMessages = result.errorData.detail.joinToString("\n") { detail ->
val field = detail.loc.lastOrNull() ?: "Unknown field"
"$field: ${detail.msg}"
}
// Show the errors in a dialog
AlertDialog.Builder(this@RegisterActivity)
.setTitle("Validation Errors")
.setMessage(errorMessages)
.setPositiveButton("OK", null)
.show()
}
is RegisterResult.UserAlreadyExists -> {
Toast.makeText(this@RegisterActivity, "User already exists. Please log in.", Toast.LENGTH_LONG).show()
}
is RegisterResult.UnknownError -> {
Toast.makeText(this@RegisterActivity, "Registration failed. Try again later.", Toast.LENGTH_LONG).show()
}
}
}
}
private fun navigateToLoginActivity() {

View file

@ -32,6 +32,8 @@ object RetrofitClient {
fun initialize(context: Context) {
val authInterceptor = AuthInterceptor(context)
// TODO: Add another interceptor that makes sure the api remains reachable
// (or modify AuthInterceptor & probably also rename it)
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(authInterceptor) // Adds the AuthInterceptor
.build()

View file

@ -7,6 +7,15 @@ import com.p_vacho.neat_calendar.api.models.RegisterResponse
import com.p_vacho.neat_calendar.api.models.SessionResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import retrofit2.HttpException
import com.google.gson.Gson
sealed class RegisterResult {
data class Success(val response: RegisterResponse) : RegisterResult()
data class ValidationError(val errorData: com.p_vacho.neat_calendar.auth.ValidationError) : RegisterResult()
object UserAlreadyExists : RegisterResult()
data class UnknownError(val exception: Throwable) : RegisterResult()
}
class AuthRepository(private val tokenManager: TokenManager) {
@ -27,19 +36,37 @@ class AuthRepository(private val tokenManager: TokenManager) {
}
// Register user (doesn't perform a login)
suspend fun register(username: String, password: String, email: String): RegisterResponse? {
suspend fun register(username: String, password: String, email: String): RegisterResult {
return withContext(Dispatchers.IO) {
try {
val response = RetrofitClient.authService.register(RegisterRequest(username, password, email))
Log.i("API", "Register request succesful")
response
Log.i("API", "Register request successful")
RegisterResult.Success(response)
} catch (e: HttpException) {
when (e.code()) {
422 -> {
val errorBody = e.response()?.errorBody()?.string()
val validationError = Gson().fromJson(errorBody, ValidationError::class.java)
Log.w("API", "Validation error: $validationError")
RegisterResult.ValidationError(validationError)
}
409 -> {
Log.w("API", "User already exists")
RegisterResult.UserAlreadyExists
}
else -> {
Log.w("API", "Register request failed (${e.message()})", e)
RegisterResult.UnknownError(e)
}
}
} catch (e: Exception) {
Log.w("API", "Register request failed", e)
null
Log.e("API", "Unexpected error during registration", e)
RegisterResult.UnknownError(e)
}
}
}
// Validate the access token by fetching session info
suspend fun validateAccessToken(): SessionResponse? {
return withContext(Dispatchers.IO) {

View file

@ -0,0 +1,20 @@
package com.p_vacho.neat_calendar.auth
/**
* Error data from HTTP 422 (Unprocessable entity) responses.
*/
data class ValidationError(
val detail: List<ValidationErrorDetail>
)
data class ValidationErrorDetail(
val type: String,
val loc: List<String>,
val msg: String,
val input: String?,
val ctx: ValidationErrorContext?
)
data class ValidationErrorContext(
val reason: String
)