refactor: throw proper error on registration conflict, handle properly

This commit is contained in:
Phan Huy Tran 2025-05-14 10:32:44 +02:00
commit b4351ceaea
4 changed files with 93 additions and 45 deletions

View file

@ -2,6 +2,7 @@ package de.szut.casino.exceptionHandling;
import de.szut.casino.exceptionHandling.exceptions.InsufficientFundsException; import de.szut.casino.exceptionHandling.exceptions.InsufficientFundsException;
import de.szut.casino.exceptionHandling.exceptions.UserNotFoundException; import de.szut.casino.exceptionHandling.exceptions.UserNotFoundException;
import jakarta.persistence.EntityExistsException;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ControllerAdvice;
@ -24,4 +25,10 @@ public class GlobalExceptionHandler {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
} }
@ExceptionHandler(EntityExistsException.class)
public ResponseEntity<?> handleEntityExistsException(EntityExistsException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.CONFLICT);
}
} }

View file

@ -1,6 +1,7 @@
package de.szut.casino.user; package de.szut.casino.user;
import de.szut.casino.user.dto.CreateUserDto; import de.szut.casino.user.dto.CreateUserDto;
import jakarta.persistence.EntityExistsException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
@ -19,11 +20,11 @@ public class UserService {
public UserEntity createUser(CreateUserDto createUserDto) { public UserEntity createUser(CreateUserDto createUserDto) {
if (userRepository.existsByUsername(createUserDto.getUsername())) { if (userRepository.existsByUsername(createUserDto.getUsername())) {
throw new IllegalArgumentException("Username is already taken"); throw new EntityExistsException("Username is already taken");
} }
if (userRepository.existsByEmail(createUserDto.getEmail())) { if (userRepository.existsByEmail(createUserDto.getEmail())) {
throw new IllegalArgumentException("Email is already in use"); throw new EntityExistsException("Email is already in use");
} }
UserEntity user = new UserEntity( UserEntity user = new UserEntity(

View file

@ -2,9 +2,11 @@
<div class="modal-card max-w-md w-full"> <div class="modal-card max-w-md w-full">
<h2 class="modal-heading text-center">Konto erstellen</h2> <h2 class="modal-heading text-center">Konto erstellen</h2>
<div *ngIf="errorMessage" class="bg-accent-red text-white p-4 rounded mb-4"> @if (errorMessage()) {
{{ errorMessage }} <div class="bg-accent-red text-white p-4 rounded mb-4">
</div> {{ errorMessage() }}
</div>
}
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()" class="space-y-4"> <form [formGroup]="registerForm" (ngSubmit)="onSubmit()" class="space-y-4">
<div> <div>
@ -14,18 +16,26 @@
type="email" type="email"
formControlName="email" formControlName="email"
class="w-full px-4 py-2.5 bg-deep-blue-light/50 text-white rounded-lg my-1 border border-deep-blue-light/30 focus:border-emerald/50 focus:ring-1 focus:ring-emerald/50 outline-none transition-all duration-200" class="w-full px-4 py-2.5 bg-deep-blue-light/50 text-white rounded-lg my-1 border border-deep-blue-light/30 focus:border-emerald/50 focus:ring-1 focus:ring-emerald/50 outline-none transition-all duration-200"
[ngClass]="{ 'border-accent-red': fieldErrors()['email'] }"
placeholder="Gib deine E-Mail-Adresse ein" placeholder="Gib deine E-Mail-Adresse ein"
/> />
<div @if (fieldErrors()['email']) {
*ngIf="form['email'].touched && form['email'].errors" <div class="text-accent-red mt-1 text-sm">
class="text-accent-red mt-1 text-sm" {{ fieldErrors()['email'] }}
> </div>
<span *ngIf="form['email'].errors?.['required']">E-Mail ist erforderlich</span> }
<span *ngIf="form['email'].errors?.['email']">
Bitte gib eine gültige E-Mail-Adresse ein @if (!fieldErrors()['email'] && form['email'].touched && form['email'].errors) {
</span> <div class="text-accent-red mt-1 text-sm">
</div> @if (form['email'].errors['required']) {
<span>E-Mail ist erforderlich</span>
}
@if (form['email'].errors['email']) {
<span>Bitte gib eine gültige E-Mail-Adresse ein</span>
}
</div>
}
</div> </div>
<div> <div>
@ -37,18 +47,26 @@
type="text" type="text"
formControlName="username" formControlName="username"
class="w-full px-4 py-2.5 bg-deep-blue-light/50 text-white rounded-lg my-1 border border-deep-blue-light/30 focus:border-emerald/50 focus:ring-1 focus:ring-emerald/50 outline-none transition-all duration-200" class="w-full px-4 py-2.5 bg-deep-blue-light/50 text-white rounded-lg my-1 border border-deep-blue-light/30 focus:border-emerald/50 focus:ring-1 focus:ring-emerald/50 outline-none transition-all duration-200"
[ngClass]="{ 'border-accent-red': fieldErrors()['username'] }"
placeholder="Wähle einen Benutzernamen" placeholder="Wähle einen Benutzernamen"
/> />
<div @if (fieldErrors()['username']) {
*ngIf="form['username'].touched && form['username'].errors" <div class="text-accent-red mt-1 text-sm">
class="text-accent-red mt-1 text-sm" {{ fieldErrors()['username'] }}
> </div>
<span *ngIf="form['username'].errors?.['required']">Benutzername ist erforderlich</span> }
<span *ngIf="form['username'].errors?.['minlength']">
Benutzername muss mindestens 3 Zeichen haben @if (!fieldErrors()['username'] && form['username'].touched && form['username'].errors) {
</span> <div class="text-accent-red mt-1 text-sm">
</div> @if (form['username'].errors['required']) {
<span>Benutzername ist erforderlich</span>
}
@if (form['username'].errors['minlength']) {
<span>Benutzername muss mindestens 3 Zeichen haben</span>
}
</div>
}
</div> </div>
<div> <div>
@ -63,24 +81,25 @@
placeholder="Erstelle ein Passwort" placeholder="Erstelle ein Passwort"
/> />
<div @if (form['password'].touched && form['password'].errors) {
*ngIf="form['password'].touched && form['password'].errors" <div class="text-accent-red mt-1 text-sm">
class="text-accent-red mt-1 text-sm" @if (form['password'].errors['required']) {
> <span>Passwort ist erforderlich</span>
<span *ngIf="form['password'].errors?.['required']">Passwort ist erforderlich</span> }
<span *ngIf="form['password'].errors?.['minlength']"> @if (form['password'].errors['minlength']) {
Passwort muss mindestens 6 Zeichen haben <span>Passwort muss mindestens 6 Zeichen haben</span>
</span> }
</div> </div>
}
</div> </div>
<div class="pt-2"> <div class="pt-2">
<button <button
type="submit" type="submit"
[disabled]="registerForm.invalid || isLoading" [disabled]="registerForm.invalid || isLoading()"
class="button-primary w-full py-2.5 rounded" class="button-primary w-full py-2.5 rounded"
> >
{{ isLoading ? 'Konto wird erstellt...' : 'Registrieren' }} {{ isLoading() ? 'Konto wird erstellt...' : 'Registrieren' }}
</button> </button>
</div> </div>
</form> </form>

View file

@ -1,9 +1,10 @@
import { Component } from '@angular/core'; import { Component, signal } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { Router, RouterLink } from '@angular/router'; import { Router, RouterLink } from '@angular/router';
import { RegisterRequest } from '../../../model/auth/RegisterRequest'; import { RegisterRequest } from '../../../model/auth/RegisterRequest';
import { AuthService } from '@service/auth.service'; import { AuthService } from '@service/auth.service';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { HttpErrorResponse } from '@angular/common/http';
@Component({ @Component({
selector: 'app-register', selector: 'app-register',
@ -13,8 +14,9 @@ import { CommonModule } from '@angular/common';
}) })
export class RegisterComponent { export class RegisterComponent {
registerForm: FormGroup; registerForm: FormGroup;
errorMessage = ''; errorMessage = signal<string>('');
isLoading = false; isLoading = signal<boolean>(false);
fieldErrors = signal<{ [key: string]: string }>({});
constructor( constructor(
private fb: FormBuilder, private fb: FormBuilder,
@ -37,8 +39,9 @@ export class RegisterComponent {
return; return;
} }
this.isLoading = true; this.isLoading.set(true);
this.errorMessage = ''; this.errorMessage.set('');
this.fieldErrors.set({});
const registerRequest: RegisterRequest = { const registerRequest: RegisterRequest = {
email: this.form['email'].value, email: this.form['email'].value,
@ -59,15 +62,33 @@ export class RegisterComponent {
this.router.navigate(['/home']); this.router.navigate(['/home']);
}, },
error: () => { error: () => {
this.isLoading = false; this.isLoading.set(false);
this.errorMessage = this.errorMessage.set(
'Registration successful but failed to login automatically. Please log in manually.'; 'Registration successful but failed to login automatically. Please log in manually.'
);
}, },
}); });
}, },
error: (err) => { error: (err: HttpErrorResponse) => {
this.isLoading = false; this.isLoading.set(false);
this.errorMessage = err.error?.message || 'Failed to register. Please try again.';
if (err.status === 409) {
const errorResponse = err.error;
if (errorResponse?.message === 'Email is already in use') {
this.fieldErrors.update((errors) => ({
...errors,
email: 'Diese E-Mail-Adresse wird bereits verwendet.',
}));
} else if (errorResponse?.message === 'Username is already taken') {
this.fieldErrors.update((errors) => ({
...errors,
username: 'Dieser Benutzername ist bereits vergeben.',
}));
}
} else {
this.errorMessage.set(err.error?.message || 'Failed to register. Please try again.');
}
}, },
}); });
} }