From 0e9e729fdf9f375b9faace2b802134d0239adb0a Mon Sep 17 00:00:00 2001 From: jank Date: Thu, 12 Jun 2025 15:28:31 +0200 Subject: [PATCH] chore: Remove old Anhang and add some code to the docs --- projektdokumentation/Abkuerzungen.tex | 1 - projektdokumentation/Anhang.tex | 67 ++--- projektdokumentation/Inhalt/Coinflip.tex | 5 +- projektdokumentation/Inhalt/Deployment.tex | 2 +- .../Inhalt/Projektarchitektur.tex | 2 +- .../Listings/CoinflipComponent.ts | 247 ++++++++++++++++++ .../Listings/CoinflipController.java | 38 +++ .../Listings/CoinflipService.java | 35 +++ projektdokumentation/Listings/UserEntity.java | 92 +++++++ .../Listings/application.properties | 53 ++++ 10 files changed, 489 insertions(+), 53 deletions(-) create mode 100644 projektdokumentation/Listings/CoinflipComponent.ts create mode 100644 projektdokumentation/Listings/CoinflipController.java create mode 100644 projektdokumentation/Listings/CoinflipService.java create mode 100644 projektdokumentation/Listings/UserEntity.java create mode 100644 projektdokumentation/Listings/application.properties diff --git a/projektdokumentation/Abkuerzungen.tex b/projektdokumentation/Abkuerzungen.tex index ebc9cd5..7ebc15d 100644 --- a/projektdokumentation/Abkuerzungen.tex +++ b/projektdokumentation/Abkuerzungen.tex @@ -35,6 +35,5 @@ \acro{URL}{Uniform Resource Locator}\acused{URL} \acro{VM}{Virtual Machine} \acro{XML}{Extensible Markup Language} - \acro{API}{Application Programming Interface} \acro{JWT}{JSON Web Token} \end{acronym} diff --git a/projektdokumentation/Anhang.tex b/projektdokumentation/Anhang.tex index 11b27aa..77b18f9 100644 --- a/projektdokumentation/Anhang.tex +++ b/projektdokumentation/Anhang.tex @@ -13,66 +13,35 @@ \clearpage -\subsection{Use Case-Diagramm} -\label{app:UseCase} -\begin{figure}[htb] -\centering -\includegraphicsKeepAspectRatio{UseCase.pdf}{1.3} -\caption{Use Case-Diagramm} -\end{figure} +\subsection{Implementierungsbeispiele} +\label{app:CodeSchichten} + +\subsubsection{Frontend-Schicht: Angular Component} +\label{app:FrontendComponent} +\lstinputlisting[language=C, caption={Angular TypeScript Component - Coinflip Game}]{Listings/CoinflipComponent.ts} \clearpage -\subsection{Amortisation} -\label{app:Amortisation} -Der Zeitpunkt der Amortisation wird als Schnittpunkt der beiden Geraden angegeben. - -\begin{figure}[htb] - \centering - \includegraphicsKeepAspectRatio{amortisationgrafik.png}{1} - \caption{Grafische Darstellung der Amortisation} -\end{figure} +\subsubsection{Controller-Schicht: Spring Boot REST Controller} +\label{app:ControllerSchicht} +\lstinputlisting[language=java, caption={Spring Boot REST Controller - Coinflip}]{Listings/CoinflipController.java} \clearpage -\subsection{composer.json Konfiguration für neusta-m2-intex-client} -\label{app:ComposerJson} -\lstinputlisting[language=json, caption={Konfiguration für neusta-m2-intex-client}]{Listings/composer.json} +\subsubsection{Service-Schicht: Business Logic} +\label{app:ServiceSchicht} +\lstinputlisting[language=java, caption={Service-Klasse mit Geschäftslogik - Coinflip}]{Listings/CoinflipService.java} \clearpage -\subsection{Deklaration zur Anlage einer SQL Tabelle im Magento 2 Umfeld} -\label{app:InstallData} -\lstinputlisting[language=xml, caption={Deklaration zur Anlage einer SQL Tabelle im Magento 2 Umfeld}]{Listings/InstallData.xml} +\subsubsection{Persistierung-Schicht: JPA Entity} +\label{app:PersistierungSchicht} +\lstinputlisting[language=java, caption={JPA Entity - Benutzer}]{Listings/UserEntity.java} \clearpage -\subsection{Klasse: Factory} -\label{app:Factory} -\lstinputlisting[language=php, caption={Klasse: Factory}]{Listings/Factory.php} - -\clearpage - -\subsection{Klasse: CustomerConnection} -\label{app:CustomerConnection} -\lstinputlisting[language=php, caption={Klasse: CustomerConnection}]{Listings/CustomerConnection.php} - -\clearpage - -\subsection{Klasse: Connection} -\label{app:Connection} -\lstinputlisting[language=php, caption={Abstrakte Klasse: Connection}]{Listings/Connection.php} - -\clearpage - -\subsection{Klasse: CustomerDataController} -\label{app:CustomerDataController} -\lstinputlisting[language=php, caption={Klasse: CustomerDataController}]{Listings/CustomerDataController.php} - -\clearpage - -\subsection{UnitTest: FactoryTest} -\label{app:UnitTest} -\lstinputlisting[language=php, caption={Unit Test der Klasse: Factory}]{Listings/UnitTest.php} +\subsubsection{Konfiguration: Application Properties} +\label{app:Konfiguration} +\lstinputlisting[caption={Spring Boot Anwendungskonfiguration}]{Listings/application.properties} \clearpage diff --git a/projektdokumentation/Inhalt/Coinflip.tex b/projektdokumentation/Inhalt/Coinflip.tex index 18ea1c5..a656019 100644 --- a/projektdokumentation/Inhalt/Coinflip.tex +++ b/projektdokumentation/Inhalt/Coinflip.tex @@ -36,4 +36,7 @@ Das Spielergebnis wird strukturiert an das Frontend übermittelt und enthält: \item Gewinnstatus (gewonnen/verloren) \item Auszahlungsbetrag (bei Gewinn: 2x Einsatz) \item Geworfene Münzseite -\end{itemize} \ No newline at end of file +\end{itemize} + +\subsubsection{Implementierungsdetails} +Die vollständige Implementierung der Coinflip-Funktionalität umfasst verschiedene Architekturschichten: Das Angular Frontend-Component (siehe \ref{app:FrontendComponent}), den Spring Boot REST Controller (siehe \ref{app:ControllerSchicht}) und die Service-Schicht mit der Geschäftslogik (siehe \ref{app:ServiceSchicht}). Zusätzlich wird die Benutzer-Entity (siehe \ref{app:PersistierungSchicht}) für die Guthaben-Verwaltung verwendet. \ No newline at end of file diff --git a/projektdokumentation/Inhalt/Deployment.tex b/projektdokumentation/Inhalt/Deployment.tex index 9b1a671..bf19423 100644 --- a/projektdokumentation/Inhalt/Deployment.tex +++ b/projektdokumentation/Inhalt/Deployment.tex @@ -3,5 +3,5 @@ Es gibt zwei Server auf denen Instanzen der Applikation laufen. \begin{itemize} \item \textbf{\href{https://casino.simonis.lol/}{Entwicklungsserver}:} Auf dem Entwicklungsserver läuft eine Instanz der Applikation, die für die Entwicklung und das Testen von neuen Features genutzt wird. Diese Instanz ist Lokal bei Constantin gehostet und wird durch einen Cloudflare-Tunnel öffentlich zugänglich gemacht. - \item \textbf{\href{https://trustworthy.casino/}{Produktionsserver}:} Auf dem Produktionsserver läuft die finale Version der Applikation, die für die Nutzer zugänglich ist. Diese Instanz ist öffentlich zugänglich und wird von den Nutzern genutzt. Diese Instanz ist auf einem gemieteten Server gehostet. Die Applikation wird durch eine Nginx Reverse-Proxy bereitgestellt, die Anfragen an die \acs{API} und das Frontend weiterleitet und SSL-Zertifikate verwaltet. + \item \textbf{\href{https://trustworthy.casino/}{Produktionsserver}:} Auf dem Produktionsserver läuft die finale Version der Applikation, die für die Nutzer zugänglich ist. Diese Instanz ist öffentlich zugänglich und wird von den Nutzern genutzt. Diese Instanz ist auf einem gemieteten Server gehostet. Die Applikation wird durch eine Nginx Reverse-Proxy bereitgestellt, die Anfragen an die \acs{API} und das Frontend weiterleitet und SSL-Zertifikate verwaltet. Die Konfiguration der Anwendung erfolgt über Umgebungsvariablen und Properties-Dateien (siehe \ref{app:Konfiguration}). \end{itemize} \ No newline at end of file diff --git a/projektdokumentation/Inhalt/Projektarchitektur.tex b/projektdokumentation/Inhalt/Projektarchitektur.tex index d560713..4480dd6 100644 --- a/projektdokumentation/Inhalt/Projektarchitektur.tex +++ b/projektdokumentation/Inhalt/Projektarchitektur.tex @@ -27,7 +27,7 @@ Services übernehmen die Kommunikation mit dem Backend und kapseln die Geschäft \subsubsection{Backend-Architektur} Das Backend implementiert eine klassische mehrschichtige Architektur, die eine klare Trennung der Verantwortlichkeiten gewährleistet. Die Controller-Schicht stellt die REST-\acs{API}-Endpunkte bereit und behandelt \acs{HTTP}-Anfragen. Die Service-Schicht enthält die Geschäftslogik und orchestriert verschiedene Use Cases. -Die Repository-Schicht abstrahiert den Datenzugriff und verwendet Spring Data JPA für die Kommunikation mit der Datenbank. Entity-Klassen repräsentieren die Domain-Modelle und bilden die Datenbankstrukturen ab. +Die Repository-Schicht abstrahiert den Datenzugriff und verwendet Spring Data JPA für die Kommunikation mit der Datenbank. Entity-Klassen repräsentieren die Domain-Modelle und bilden die Datenbankstrukturen ab. Eine detaillierte Darstellung der verschiedenen Architekturschichten mit konkreten Code-Beispielen findet sich im Anhang (siehe \ref{app:CodeSchichten}). \subsection{Datenarchitektur} Die Datenbank folgt einem relationalen Design mit klar definierten Entitätsbeziehungen. Das Schema gliedert sich in mehrere Hauptbereiche: Der User Management Bereich verwaltet Benutzerkonten und Benutzerprofile. Spielbezogene Daten wie Spielstände, Wetten und Ergebnisse werden in separaten Tabellen gespeichert, um die Integrität der Spiellogik zu gewährleisten. diff --git a/projektdokumentation/Listings/CoinflipComponent.ts b/projektdokumentation/Listings/CoinflipComponent.ts new file mode 100644 index 0000000..da53f97 --- /dev/null +++ b/projektdokumentation/Listings/CoinflipComponent.ts @@ -0,0 +1,247 @@ +import { NgClass, NgIf, CurrencyPipe, CommonModule } from '@angular/common'; +import { HttpClient } from '@angular/common/http'; +import { FormsModule } from '@angular/forms'; +import { + ChangeDetectionStrategy, + Component, + ElementRef, + inject, + OnInit, + signal, + ViewChild, +} from '@angular/core'; +import { AnimatedNumberComponent } from '@blackjack/components/animated-number/animated-number.component'; +import { catchError, finalize } from 'rxjs/operators'; +import { of } from 'rxjs'; +import { AuthService } from '@service/auth.service'; +import { AudioService } from '@shared/services/audio.service'; +import { CoinflipGame, CoinflipRequest } from './models/coinflip.model'; + +@Component({ + selector: 'app-coinflip', + standalone: true, + imports: [AnimatedNumberComponent, CurrencyPipe, FormsModule, CommonModule, NgIf, NgClass], + templateUrl: './coinflip.component.html', + styleUrl: './coinflip.component.css', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export default class CoinflipComponent implements OnInit { + currentBet = signal(10); + balance = signal(0); + gameInProgress = signal(false); + isActionInProgress = signal(false); + gameResult = signal(null); + betInputValue = signal(10); + errorMessage = signal(''); + isInvalidBet = signal(false); + + @ViewChild('coinElement') coinElement?: ElementRef; + + audioService = inject(AudioService); + authService = inject(AuthService); + private http = inject(HttpClient); + + private coinflipSound?: HTMLAudioElement; + + ngOnInit(): void { + // Abonniere Benutzerupdates fuer Echtzeitaktualisierungen des Guthabens + this.authService.userSubject.subscribe((user) => { + if (user) { + this.balance.set(user.balance); + } + }); + + // Initialisiere Muenzwurf-Sound + this.coinflipSound = new Audio('/sounds/coinflip.mp3'); + } + + setBetAmount(percentage: number) { + const newBet = Math.floor(this.balance() * percentage); + this.betInputValue.set(newBet > 0 ? newBet : 1); + this.currentBet.set(this.betInputValue()); + } + + updateBet(event: Event) { + const inputElement = event.target as HTMLInputElement; + let value = Number(inputElement.value); + + // Setze ungueltigen Einsatz-Status zurueck + this.isInvalidBet.set(false); + + // Erzwinge Mindesteinsatz von 1 + if (value <= 0) { + value = 1; + } + + // Begrenze Einsatz auf verfuegbares Guthaben und zeige Feedback + if (value > this.balance()) { + value = this.balance(); + // Visuelles Feedback anzeigen + this.isInvalidBet.set(true); + // Zeige den Fehler kurz an + setTimeout(() => this.isInvalidBet.set(false), 800); + // Aktualisiere das Eingabefeld direkt, um dem Benutzer den maximalen Wert anzuzeigen + inputElement.value = String(value); + } + + // Aktualisiere Signale + this.betInputValue.set(value); + this.currentBet.set(value); + } + + betHeads() { + this.placeBet('HEAD'); + } + + betTails() { + this.placeBet('TAILS'); + } + + private placeBet(side: 'HEAD' | 'TAILS') { + if (this.gameInProgress() || this.isActionInProgress()) return; + + // Setze vorheriges Ergebnis zurueck + this.gameResult.set(null); + this.errorMessage.set(''); + + // Setze Spielstatus + this.gameInProgress.set(true); + this.isActionInProgress.set(true); + + // Spiele Einsatz-Sound + this.audioService.playBetSound(); + + // Erstelle Einsatz-Anfrage + const request: CoinflipRequest = { + betAmount: this.currentBet(), + coinSide: side, + }; + + // API aufrufen + this.http + .post('/backend/coinflip', request) + .pipe( + catchError((error) => { + console.error('Fehler beim Spielen von Coinflip:', error); + + if (error.status === 400 && error.error.message.includes('insufficient')) { + this.errorMessage.set('Unzureichendes Guthaben'); + } else { + this.errorMessage.set('Ein Fehler ist aufgetreten. Bitte versuche es erneut.'); + } + + this.gameInProgress.set(false); + return of(null); + }), + finalize(() => { + this.isActionInProgress.set(false); + }) + ) + .subscribe((result) => { + if (!result) return; + + console.log('API-Antwort:', result); + + // Behebe moegliche Inkonsistenzen bei der Eigenschaftenbenennung vom Backend + const fixedResult: CoinflipGame = { + isWin: result.isWin ?? result.win, + payout: result.payout, + coinSide: result.coinSide, + }; + + console.log('Korrigiertes Ergebnis:', fixedResult); + + // Spiele Muenzwurf-Animation und -Sound + this.playCoinFlipAnimation(fixedResult.coinSide); + + // Setze Ergebnis nach Abschluss der Animation + setTimeout(() => { + this.gameResult.set(fixedResult); + + // Aktualisiere Guthaben mit neuem Wert vom Auth-Service + this.authService.loadCurrentUser(); + + // Spiele Gewinn-Sound, wenn der Spieler gewonnen hat + if (fixedResult.isWin) { + this.audioService.playWinSound(); + } + + // Setze Spielstatus nach Anzeigen des Ergebnisses zurueck + setTimeout(() => { + this.gameInProgress.set(false); + }, 1500); + }, 1100); // Kurz nach Ende der Animation + }); + } + + private playCoinFlipAnimation(result: 'HEAD' | 'TAILS') { + if (!this.coinElement) return; + + const coinEl = this.coinElement.nativeElement; + + // Setze bestehende Animationen zurueck + coinEl.classList.remove('animate-to-heads', 'animate-to-tails'); + + // Setze alle Inline-Styles von vorherigen Animationen zurueck + coinEl.style.transform = ''; + + // Erzwinge Reflow, um Animation neu zu starten + void coinEl.offsetWidth; + + // Spiele Muenzwurf-Sound + if (this.coinflipSound) { + this.coinflipSound.currentTime = 0; + this.coinflipSound + .play() + .catch((err) => console.error('Fehler beim Abspielen des Sounds:', err)); + } + + // Fuege passende Animationsklasse basierend auf dem Ergebnis hinzu + if (result === 'HEAD') { + coinEl.classList.add('animate-to-heads'); + } else { + coinEl.classList.add('animate-to-tails'); + } + + console.log(`Animation angewendet fuer Ergebnis: ${result}`); + } + + /** + * Validiert Eingabe waehrend der Benutzer tippt, um ungueltige Werte zu verhindern + */ + validateBetInput(event: KeyboardEvent) { + // Erlaube Navigationstasten (Pfeile, Entf, Ruecktaste, Tab) + const navigationKeys = ['ArrowLeft', 'ArrowRight', 'Delete', 'Backspace', 'Tab']; + if (navigationKeys.includes(event.key)) { + return; + } + + // Erlaube nur Zahlen + if (!/^\d$/.test(event.key)) { + event.preventDefault(); + return; + } + + // Ermittle den Wert, der nach dem Tastendruck entstehen wuerde + const input = event.target as HTMLInputElement; + const currentValue = input.value; + const cursorPosition = input.selectionStart || 0; + const newValue = + currentValue.substring(0, cursorPosition) + + event.key + + currentValue.substring(input.selectionEnd || cursorPosition); + const numValue = Number(newValue); + + // Verhindere Werte, die groesser als das Guthaben sind + if (numValue > this.balance()) { + event.preventDefault(); + } + } + + getResultClass() { + if (!this.gameResult()) return ''; + const result = this.gameResult(); + const isWinner = result?.isWin || result?.win; + return isWinner ? 'text-emerald-500' : 'text-accent-red'; + } +} \ No newline at end of file diff --git a/projektdokumentation/Listings/CoinflipController.java b/projektdokumentation/Listings/CoinflipController.java new file mode 100644 index 0000000..21ced8e --- /dev/null +++ b/projektdokumentation/Listings/CoinflipController.java @@ -0,0 +1,38 @@ +package de.szut.casino.coinflip; + +import de.szut.casino.exceptionHandling.exceptions.InsufficientFundsException; +import de.szut.casino.exceptionHandling.exceptions.UserNotFoundException; +import de.szut.casino.shared.service.BalanceService; +import de.szut.casino.user.UserEntity; +import de.szut.casino.user.UserService; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Optional; + +@RestController +public class CoinflipController { + private final UserService userService; + private final BalanceService balanceService; + private final CoinflipService coinflipService; + + public CoinflipController(UserService userService, BalanceService balanceService, CoinflipService coinflipService) { + this.userService = userService; + this.balanceService = balanceService; + this.coinflipService = coinflipService; + } + + @PostMapping("/coinflip") + public ResponseEntity coinFlip(@RequestBody @Valid CoinflipDto coinflipDto) { + UserEntity user = userService.getCurrentUser(); + + if (!this.balanceService.hasFunds(user, coinflipDto)) { + throw new InsufficientFundsException(); + } + + return ResponseEntity.ok(coinflipService.play(user, coinflipDto)); + } +} \ No newline at end of file diff --git a/projektdokumentation/Listings/CoinflipService.java b/projektdokumentation/Listings/CoinflipService.java new file mode 100644 index 0000000..c200361 --- /dev/null +++ b/projektdokumentation/Listings/CoinflipService.java @@ -0,0 +1,35 @@ +package de.szut.casino.coinflip; + +import de.szut.casino.shared.service.BalanceService; +import de.szut.casino.user.UserEntity; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.Random; + +@Service +public class CoinflipService { + private final Random random; + private final BalanceService balanceService; + + public CoinflipService(BalanceService balanceService, Random random) { + this.balanceService = balanceService; + this.random = random; + } + + public CoinflipResult play(UserEntity user, CoinflipDto coinflipDto) { + this.balanceService.subtractFunds(user, coinflipDto.getBetAmount()); + + CoinSide coinSide = this.random.nextBoolean() ? CoinSide.HEAD : CoinSide.TAILS; + CoinflipResult coinflipResult = new CoinflipResult(false, BigDecimal.ZERO, coinSide); + if (coinSide == coinflipDto.getCoinSide()) { + coinflipResult.setWin(true); + + BigDecimal payout = coinflipDto.getBetAmount().multiply(BigDecimal.TWO); + this.balanceService.addFunds(user, payout); + coinflipResult.setPayout(payout); + } + + return coinflipResult; + } +} \ No newline at end of file diff --git a/projektdokumentation/Listings/UserEntity.java b/projektdokumentation/Listings/UserEntity.java new file mode 100644 index 0000000..61ad04f --- /dev/null +++ b/projektdokumentation/Listings/UserEntity.java @@ -0,0 +1,92 @@ +package de.szut.casino.user; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.math.BigDecimal; + +@Setter +@Getter +@Entity +@NoArgsConstructor +public class UserEntity { + @Id + @GeneratedValue + private Long id; + + @Version + private Long version; + + @Column(unique = true) + private String email; + + @Column(unique = true) + private String username; + + private String password; + + @Column(precision = 19, scale = 2) + private BigDecimal balance; + + private Boolean emailVerified = false; + + private String verificationToken; + + private String passwordResetToken; + + @Enumerated(EnumType.STRING) + private AuthProvider provider = AuthProvider.LOCAL; + + private String providerId; + + public UserEntity(String email, String username, String password, BigDecimal balance, String verificationToken) { + this.email = email; + this.username = username; + this.password = password; + this.balance = balance; + this.verificationToken = verificationToken; + } + + public UserEntity(String email, String username, AuthProvider provider, String providerId, BigDecimal balance) { + this.email = email; + this.username = username; + this.provider = provider; + this.providerId = providerId; + this.balance = balance; + this.emailVerified = true; // OAuth providers verify emails + } + + public void addBalance(BigDecimal amountToAdd) { + if (amountToAdd == null || amountToAdd.compareTo(BigDecimal.ZERO) <= 0) { + return; + } + + if (this.balance == null) { + this.balance = BigDecimal.ZERO; + } + + this.balance = this.balance.add(amountToAdd); + } + + public void subtractBalance(BigDecimal amountToSubtract) { + if (amountToSubtract == null || amountToSubtract.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("Amount to subtract must be positive."); + } + + if (this.balance == null) { + this.balance = BigDecimal.ZERO; + } + + if (this.balance.compareTo(amountToSubtract) < 0) { + throw new IllegalStateException("Insufficient funds to subtract " + amountToSubtract); + } + + this.balance = this.balance.subtract(amountToSubtract); + } + + public String getEmailAddress() { + return "${name} <${email}>".replace("${name}", this.username).replace("${email}", this.email); + } +} \ No newline at end of file diff --git a/projektdokumentation/Listings/application.properties b/projektdokumentation/Listings/application.properties new file mode 100644 index 0000000..df6f5cc --- /dev/null +++ b/projektdokumentation/Listings/application.properties @@ -0,0 +1,53 @@ +spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:postgresdb} +spring.datasource.username=${DB_USER:postgres_user} +spring.datasource.password=${DB_PASS:postgres_pass} +server.port=${HTTP_PORT:8080} +spring.jpa.hibernate.ddl-auto=update +stripe.secret.key=${STRIPE_SECRET_KEY:sk_test_51QrePYIvCfqz7ANgqam8rEwWcMeKiLOof3j6SCMgu2sl4sESP45DJxca16mWcYo1sQaiBv32CMR6Z4AAAGQPCJo300ubuZKO8I} +stripe.webhook.secret=${STRIPE_WEBHOOK_SECRET:whsec_746b6a488665f6057118bdb4a2b32f4916f16c277109eeaed5e8f8e8b81b8c15} + +app.frontend-host=${FE_URL:http://localhost:4200} + +app.mail.authentication=${MAIL_AUTHENTICATION:false} +app.mail.host=${MAIL_HOST:localhost} +app.mail.port=${MAIL_PORT:1025} +app.mail.username=${MAIL_USER:null} +app.mail.password=${MAIL_PASS:null} +app.mail.from-address=${MAIL_FROM:casino@localhost} +app.mail.protocol=${MAIL_PROTOCOL:smtp} + +spring.application.name=casino + +# JWT Configuration +jwt.secret=${JWT_SECRET:5367566B59703373367639792F423F4528482B4D6251655468576D5A71347437} +jwt.expiration.ms=${JWT_EXPIRATION_MS:86400000} + +# Logging +logging.level.org.springframework.security=DEBUG + +# Swagger +springdoc.swagger-ui.path=swagger +springdoc.swagger-ui.try-it-out-enabled=true + +# GitHub OAuth2 Configuration +spring.security.oauth2.client.registration.github.client-id=${GITHUB_CLIENT_ID:Ov23lingzZsPn1wwACoK} +spring.security.oauth2.client.registration.github.client-secret=${GITHUB_CLIENT_SECRET:4b327fb3b1ab67584a03bcb9d53fa6439fbccad7} +spring.security.oauth2.client.registration.github.redirect-uri=${app.frontend-host}/oauth2/callback/github +spring.security.oauth2.client.registration.github.scope=user:email,read:user +spring.security.oauth2.client.provider.github.authorization-uri=https://github.com/login/oauth/authorize +spring.security.oauth2.client.provider.github.token-uri=https://github.com/login/oauth/access_token +spring.security.oauth2.client.provider.github.user-info-uri=https://api.github.com/user +spring.security.oauth2.client.provider.github.user-name-attribute=login + +# OAuth Success and Failure URLs +app.oauth2.authorizedRedirectUris=${app.frontend-host}/auth/oauth2/callback + +# Google OAuth2 Configuration +spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID:350791038883-c1r7v4o793itq8a0rh7dut7itm7uneam.apps.googleusercontent.com} +spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET: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} +spring.security.oauth2.client.registration.google.redirect-uri=${app.frontend-host}/oauth2/callback/google +spring.security.oauth2.client.registration.google.scope=email,profile +spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/v2/auth +spring.security.oauth2.client.provider.google.token-uri=https://oauth2.googleapis.com/token +spring.security.oauth2.client.provider.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo +spring.security.oauth2.client.provider.google.user-name-attribute=sub \ No newline at end of file