Compare commits

...

6 commits

Author SHA1 Message Date
20e0805d0e
Merge pull request 'refactor: throw proper error on registration conflict, handle properly' (!187) from refactor-register into main
All checks were successful
Release / Release (push) Successful in 58s
Release / Build Backend Image (push) Successful in 30s
Release / Build Frontend Image (push) Successful in 28s
Reviewed-on: #187
Reviewed-by: Jan K9f <jan@kjan.email>
2025-05-14 09:24:01 +00:00
Phan Huy Tran
0bab8a343c style: run prettier
All checks were successful
CI / Get Changed Files (pull_request) Successful in 7s
CI / Docker backend validation (pull_request) Successful in 11s
CI / eslint (pull_request) Successful in 34s
CI / oxlint (pull_request) Successful in 31s
CI / Docker frontend validation (pull_request) Successful in 47s
CI / Checkstyle Main (pull_request) Successful in 50s
CI / prettier (pull_request) Successful in 24s
CI / test-build (pull_request) Successful in 33s
2025-05-14 10:40:32 +02:00
Phan Huy Tran
e7e43839eb refactor: fix linter, adjust errorhandling
Some checks failed
CI / Get Changed Files (pull_request) Successful in 10s
CI / Docker backend validation (pull_request) Successful in 17s
CI / oxlint (pull_request) Successful in 28s
CI / eslint (pull_request) Successful in 40s
CI / Checkstyle Main (pull_request) Successful in 1m1s
CI / Docker frontend validation (pull_request) Successful in 1m0s
CI / prettier (pull_request) Failing after 22s
CI / test-build (pull_request) Successful in 30s
2025-05-14 10:37:22 +02:00
Phan Huy Tran
b4351ceaea refactor: throw proper error on registration conflict, handle properly 2025-05-14 10:37:22 +02:00
46e52e20cc
Merge pull request 'fix: Add concurrency rules' (!186) from concurrency into main
Reviewed-on: #186
Reviewed-by: Phan Huy Tran <ptran@noreply.localhost>
2025-05-14 08:04:21 +00:00
48119d1faf fix: Add concurrency rules
All checks were successful
CI / Get Changed Files (pull_request) Successful in 8s
CI / Docker frontend validation (pull_request) Successful in 14s
CI / Docker backend validation (pull_request) Successful in 13s
CI / eslint (pull_request) Successful in 35s
CI / oxlint (pull_request) Successful in 30s
CI / prettier (pull_request) Successful in 32s
CI / Checkstyle Main (pull_request) Successful in 53s
CI / test-build (pull_request) Successful in 40s
2025-05-14 10:00:53 +02:00
5 changed files with 99 additions and 46 deletions

View file

@ -3,6 +3,10 @@ name: CI
on: on:
pull_request: pull_request:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
changed_files: changed_files:
name: Get Changed Files name: Get Changed Files

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<Record<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,
@ -48,7 +51,6 @@ export class RegisterComponent {
this.authService.register(registerRequest).subscribe({ this.authService.register(registerRequest).subscribe({
next: () => { next: () => {
// After registration, log in the user
this.authService this.authService
.login({ .login({
usernameOrEmail: registerRequest.email, usernameOrEmail: registerRequest.email,
@ -59,15 +61,35 @@ 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 message = err.error?.message;
switch (message) {
case 'Email is already in use':
this.fieldErrors.update((errors) => ({
...errors,
email: 'Diese E-Mail-Adresse wird bereits verwendet.',
}));
break;
case 'Username is already taken':
this.fieldErrors.update((errors) => ({
...errors,
username: 'Dieser Benutzername ist bereits vergeben.',
}));
break;
}
} else {
this.errorMessage.set(err.error?.message || 'Failed to register. Please try again.');
}
}, },
}); });
} }