diff --git a/.gitea/labeler.yml b/.gitea/labeler.yml index 7cdb157..b5946ea 100644 --- a/.gitea/labeler.yml +++ b/.gitea/labeler.yml @@ -16,4 +16,5 @@ ci: docs: - changed-files: - any-glob-to-any-file: - - "docs/**" \ No newline at end of file + - "projektdokumentation/**" + diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 0408e04..7cc4ea3 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -165,6 +165,46 @@ jobs: cd frontend bun run lint + playwright: + runs-on: ubuntu-latest + name: Playwright + needs: changed_files + container: + image: git.kjan.de/actions/runner-casino-playwright:latest + if: ${{ needs.changed_files.outputs.frontend == 'true' || needs.changed_files.outputs.workflow == 'true' }} + steps: + - name: Checkout Code + uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: "temurin" # See 'Supported distributions' for available options + java-version: "23" + - name: Install bun + uses: oven-sh/setup-bun@v2 + - uses: actions/cache@v4 + working-directory: ./frontend + with: + path: | + frontend/node_modules/ + key: ${{ runner.os }}-bun- + restore-keys: | + ${{ runner.os }}-bun- + - run: bun add -g concurrently + - name: Install dependencies + run: | + cd frontend + bun install + - uses: actions/setup-node@v4 + with: + node-version: 22.12 + working-directory: ./frontend + - name: Run Playwright tests + env: + CI: true + SPRING_PROFILES_ACTIVE: inmemory + working-directory: ./frontend + run: bash -c "source $HOME/.cargo/env && bunx playwright test" + oxlint: runs-on: docker name: oxlint diff --git a/.gitea/workflows/docs.yml b/.gitea/workflows/docs.yml index 3efc73d..88f13ae 100644 --- a/.gitea/workflows/docs.yml +++ b/.gitea/workflows/docs.yml @@ -1,6 +1,7 @@ name: Build docs on: + pull_request: push: branches: [main] @@ -11,18 +12,18 @@ jobs: image: git.kjan.de/actions/runner-latex:latest env: # Edit here with the names of your latex file and directory (can use ".") - DIR: docs - FILE: projektdokumentation.tex + DIR: projektdokumentation + FILE: Projektdokumentation.tex steps: - name: Checkout uses: actions/checkout@v4 - name: LaTeX compile working-directory: ${{ env.DIR }} - run: latexmk -pdf -xelatex ${{ env.FILE }} + run: latexmk -pdf ${{ env.FILE }} - name: Upload artifacts - uses: https://git.kjan.de/actions/upload-artifact@v4 + uses: https://git.kjan.de/actions/upload-artifact@v3 # Do not upgrade with: name: Doku - path: docs/projektdokumentation.pdf + path: projektdokumentation/Projektdokumentation.pdf diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index 548c9aa..afb8f0b 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -50,11 +50,12 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server:3.5.0") implementation("org.springframework.boot:spring-boot-starter-oauth2-client:3.5.0") runtimeOnly("org.postgresql:postgresql") - implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.8") + implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9") implementation("io.jsonwebtoken:jjwt-api:0.12.6") runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6") runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.6") implementation("org.springframework.boot:spring-boot-starter-mail") + runtimeOnly("com.h2database:h2") } tasks.withType { diff --git a/backend/gradle/wrapper/gradle-wrapper.properties b/backend/gradle/wrapper/gradle-wrapper.properties index 002b867..ff23a68 100644 --- a/backend/gradle/wrapper/gradle-wrapper.properties +++ b/backend/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/backend/src/main/java/de/szut/casino/CasinoApplication.java b/backend/src/main/java/de/szut/casino/CasinoApplication.java index 9e1f3b2..5727bb5 100644 --- a/backend/src/main/java/de/szut/casino/CasinoApplication.java +++ b/backend/src/main/java/de/szut/casino/CasinoApplication.java @@ -74,8 +74,7 @@ public class CasinoApplication { rewardRepository.saveAll(Arrays.asList( commonReward, rareReward, epicReward, - premiumCommon, premiumRare, legendaryReward - )); + premiumCommon, premiumRare, legendaryReward)); basicLootBox.getRewards().add(commonReward); basicLootBox.getRewards().add(premiumRare); diff --git a/backend/src/main/java/de/szut/casino/lootboxes/RewardEntity.java b/backend/src/main/java/de/szut/casino/lootboxes/RewardEntity.java index 1abd2df..7755abd 100644 --- a/backend/src/main/java/de/szut/casino/lootboxes/RewardEntity.java +++ b/backend/src/main/java/de/szut/casino/lootboxes/RewardEntity.java @@ -25,7 +25,7 @@ public class RewardEntity { @GeneratedValue private Long id; - @Column(precision = 19, scale = 2) + @Column(precision = 19, scale = 2, name = "rewardValue") private BigDecimal value; @Column(precision = 5, scale = 2) diff --git a/backend/src/main/resources/application-inmemory.properties b/backend/src/main/resources/application-inmemory.properties new file mode 100644 index 0000000..878fc39 --- /dev/null +++ b/backend/src/main/resources/application-inmemory.properties @@ -0,0 +1,58 @@ +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect + +spring.jpa.hibernate.ddl-auto=create-drop + +server.port=${HTTP_PORT:8080} +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 + diff --git a/docs/projektdokumentation.tex b/docs/projektdokumentation.tex deleted file mode 100644 index 4d045f4..0000000 --- a/docs/projektdokumentation.tex +++ /dev/null @@ -1,1351 +0,0 @@ -\documentclass[12pt,a4paper]{report} -\usepackage[ngerman]{babel} -\usepackage[utf8]{inputenc} -\usepackage[T1]{fontenc} -\usepackage{geometry} -\usepackage{graphicx} -\usepackage{hyperref} -\usepackage{listings} -\usepackage{xcolor} -\usepackage{float} -\usepackage{caption} -\usepackage{subcaption} -\usepackage{longtable} -\usepackage{pifont} -\usepackage{booktabs} -\usepackage{fancyhdr} -\usepackage{tocloft} -\usepackage{dirtree} -\usepackage{enumitem} -\usepackage{tikz} -\usetikzlibrary{arrows.meta, positioning} - -% Seitenränder -\geometry{ - left=2.5cm, - right=2.5cm, - top=2.5cm, - bottom=2.5cm -} - -% Kopf- und Fußzeile -\pagestyle{fancy} -\fancyhf{} -\fancyhead[L]{\leftmark} -\fancyhead[R]{\thepage} -\renewcommand{\headrulewidth}{0.4pt} - -% Code-Highlighting -\definecolor{codegreen}{rgb}{0,0.6,0} -\definecolor{codegray}{rgb}{0.5,0.5,0.5} -\definecolor{codepurple}{rgb}{0.58,0,0.82} -\definecolor{backcolour}{rgb}{0.95,0.95,0.92} - -\lstdefinestyle{mystyle}{ - backgroundcolor=\color{backcolour}, - commentstyle=\color{codegreen}, - keywordstyle=\color{magenta}, - numberstyle=\tiny\color{codegray}, - stringstyle=\color{codepurple}, - basicstyle=\ttfamily\footnotesize, - breakatwhitespace=false, - breaklines=true, - captionpos=b, - keepspaces=true, - numbers=left, - numbersep=5pt, - showspaces=false, - showstringspaces=false, - showtabs=false, - tabsize=2 -} - -\lstset{style=mystyle} - -% Hyperref-Einstellungen -\hypersetup{ - colorlinks=true, - linkcolor=black, - filecolor=magenta, - urlcolor=blue, -} - -\begin{document} - -% Titelseite -\begin{titlepage} - \centering - \vspace*{2cm} - - {\Huge\bfseries Casino Gaming Platform\par} - \vspace{1.5cm} - {\Large\itshape Projektdokumentation für die IHK-Abschlussprüfung\par} - \vspace{2cm} - {\large\bfseries Fachinformatiker für Anwendungsentwicklung\par} - - \vfill - - {\large - \begin{tabular}{ll} - \textbf{Prüfling:} & [Name des Prüflings] \\ - \textbf{Prüflingsnummer:} & [Prüflingsnummer] \\ - \textbf{Ausbildungsbetrieb:} & Hitec GmbH \\ - \textbf{Projektbetreuer:} & [Betreuer] \\ - \textbf{Projektdauer:} & 70 Stunden \\ - \textbf{Abgabedatum:} & \today \\ - \end{tabular} - \par} - - \vfill -\end{titlepage} - -% Inhaltsverzeichnis -\pagenumbering{roman} -\tableofcontents -\cleardoublepage -\pagenumbering{arabic} - -% Hauptinhalt -\chapter{Beschreibung} - -\section{Umfeld} -Das Projekt wurde im Rahmen der Abschlussprüfung zum Fachinformatiker für Anwendungsentwicklung bei der Hitec GmbH entwickelt. - -\subsection{Produktportfolio der Hitec GmbH} -Die Hitec GmbH ist ein innovatives IT-Unternehmen, das sich auf die Entwicklung moderner Webanwendungen und digitaler Lösungen spezialisiert hat. Das Unternehmen deckt ein breites Spektrum an Technologien ab, von klassischen Enterprise-Anwendungen bis hin zu modernen Cloud-nativen Lösungen. - -\subsection{Weg zur Produktidee} -Die Casino Gaming Platform entstand als LF08 Projekt mit dem Ziel, eine vollständige Online-Casino-Plattform zu entwickeln. Die Produktidee basiert auf: - -\begin{itemize} - \item \textbf{Gamification-Trend:} Wachsende Nachfrage nach Online-Gaming-Plattformen - \item \textbf{Technologie-Demonstration:} Showcase moderner Full-Stack-Entwicklung (Angular 20 + Spring Boot 3.5) - \item \textbf{Bildungsziel:} Praktische Anwendung von Enterprise-Patterns und modernen Web-Technologien - \item \textbf{Marktforschung:} Integration aktueller Standards (OAuth2, Stripe, responsive Design) -\end{itemize} - -\section{Ziel} -Das Ziel dieses Projektes ist die Entwicklung eines Online-Casinos, welches verschiedene Spiele anbietet, um mit Echtgeld zu wetten. Dabei verzichten wir auf eine Auszahlungsmöglichkeit, um Einnahmen zu steigern und rechtliche Komplexitäten zu vermeiden. - -Die Plattform soll es Benutzern ermöglichen: -\begin{itemize} - \item Mit virtueller Währung verschiedene Casino-Spiele zu spielen - \item Sichere Einzahlungen über moderne Payment-Provider zu tätigen - \item Eine intuitive und responsive Benutzeroberfläche zu nutzen - \item Transparente Transaktionshistorien einzusehen -\end{itemize} - -\chapter{Vorbereitung} - -\section{Ist-Analyse} -Detaillierte Beschreibung des Problems oder der Marktlücke, welche unser Produkt lösen/füllen soll. - -\subsection{Identifizierte Marktlücken} -\begin{enumerate} - \item \textbf{Technische Komplexität:} Viele Casino-Plattformen nutzen veraltete Technologien - \item \textbf{Benutzerfreundlichkeit:} Schlechte mobile Erfahrung und komplexe Registrierung - \item \textbf{Sicherheitsstandards:} Mangelnde OAuth2-Integration und moderne Authentifizierung - \item \textbf{Payment-Integration:} Umständliche Einzahlungsprozesse ohne moderne Payment-APIs - \item \textbf{Transparenz:} Fehlende Nachvollziehbarkeit von Transaktionen und Spielverläufen -\end{enumerate} - -\section{Soll-Analyse} -\subsection{Spieleportfolio} -\begin{itemize} - \item Blackjack (mit Split/Double Down) - \item Slots mit konfigurierbarer RTP - \item Dice, Coinflip, Lootboxes - \item Geplant: Poker, Plinko -\end{itemize} - -\subsection{Benutzerverwaltung} -\begin{itemize} - \item Multi-Provider OAuth2 (GitHub, Google) - \item JWT-basierte Authentifizierung - \item E-Mail-Verifizierung und Passwort-Reset -\end{itemize} - -\subsection{Finanzsystem} -\begin{itemize} - \item Stripe-Integration für Einzahlungen - \item Virtuelle Währung mit Präzisions-Tracking (BigDecimal) - \item Transaktionshistorie mit Status-Tracking -\end{itemize} - -\subsection{Technische Anforderungen} -\begin{itemize} - \item Responsive Design (Mobile-First) - \item Real-time Updates ohne Page-Reload - \item Sichere API-Kommunikation -\end{itemize} - -\chapter{Durchführung} - -\section{Vorgehensweise} -Für die Projektdurchführung wurde Scrum als Vorgehensmodell gewählt. Dadurch wird am Anfang des Projektes und eines Sprints festgelegt, was gemacht werden soll, und jeder ist auf dem gleichen Wissensstand. Des Weiteren gibt es tägliche Updates vom Fortschritt, Probleme können schnell erkannt und angesprochen werden. Dazu kann man auch neue Anforderungen flexibel mit einbringen. - -\subsection{Verwendete Tools und Methoden} -\begin{itemize} - \item \textbf{Scrum:} Agile Projektmanagement-Methodik - \item \textbf{Jira:} Ticketing und Sprint-Planning - \item \textbf{Git:} Versionskontrolle mit Feature-Branch-Workflow - \item \textbf{CI/CD:} Automatisierte Build- und Deployment-Pipelines -\end{itemize} - -\section{Umsetzung} - -\subsection{Art der Datenhaltung} -PostgreSQL mit JPA/Hibernate ORM wurde als Datenbanklösung gewählt: -\begin{itemize} - \item \textbf{Verbindung:} JDBC mit Connection Pooling (HikariCP) - \item \textbf{Schema:} Domain-driven Design mit klarer Trennung - \item \textbf{Transaktionen:} @Transactional Boundaries für Konsistenz - \item \textbf{Migration:} Flyway für Datenbank-Versionierung -\end{itemize} - -\subsection{Klassenmodell} - -\subsubsection{Backend (Java)} -\dirtree{% -.1 Entity Layer. -.2 UserEntity. -.2 BlackJackGameEntity. -.2 TransactionEntity. -.2 LootBoxEntity. -.1 Repository Layer. -.2 Spring Data JPA. -.1 Service Layer. -.2 Business Logic. -.3 BlackJackService. -.3 BalanceService. -.3 DepositService. -.1 Controller Layer. -.2 REST APIs. -.1 Security Layer. -.2 JWT + OAuth2. -} - -\subsubsection{Frontend (TypeScript)} -\dirtree{% -.1 Models. -.2 User interfaces. -.2 Game interfaces. -.2 Transaction interfaces. -.1 Services. -.2 HTTP client wrappers. -.1 Components. -.2 Angular standalone components. -.1 Guards. -.2 Route protection. -} - -\subsection{Design Pattern} -\begin{enumerate} - \item \textbf{MVC Pattern:} Controller → Service → Repository - \item \textbf{Repository Pattern:} Datenzugriff-Abstraktion - \item \textbf{DTO Pattern:} API-Grenze mit Datenkapselung - \item \textbf{Strategy Pattern:} OAuth2-Provider (GitHub/Google) - \item \textbf{Observer Pattern:} Angular Signals für reaktive Updates -\end{enumerate} - -\subsection{Zusammenarbeit der einzelnen Module} - -\subsubsection{Backend-Architektur} -\begin{verbatim} -Security Layer ← JWT Filter ← Controllers ← Services ← Repositories ← Database - ↓ -OAuth2 Integration (GitHub/Google) - ↓ -Payment Integration (Stripe Webhooks) -\end{verbatim} - -\subsubsection{Frontend-Architektur} -\begin{verbatim} -Guards → Components → Services → HTTP Interceptors → Backend APIs - ↓ -Angular Router → Lazy Loading → Feature Modules -\end{verbatim} - -\subsection{Systemarchitektur} -Die Casino Gaming Platform basiert auf einer modernen Microservice-Architektur mit klarer Trennung zwischen Frontend und Backend. - -\subsubsection{Gesamtarchitektur} -\begin{tikzpicture}[ - box/.style={rectangle, draw, minimum width=3cm, minimum height=1.5cm, align=center}, - ->, >=Stealth, node distance=2cm and 1.5cm -] - -% Top row -\node[box] (angular) {Angular\\Frontend}; -\node[box, right=of angular] (spring) {Spring Boot\\Backend}; -\node[box, right=of spring] (postgres) {PostgreSQL\\Database}; - -% Bottom row -\node[box, below=of angular] (keycloak) {Keycloak\\Auth Server}; -\node[box, below=of spring] (stripe) {Stripe API\\Payment}; - -% Arrows top row -\draw (angular) -- (spring); -\draw (spring) -- (postgres); - -% Arrows to bottom row -\draw (angular.south) -- (keycloak.north); -\draw (spring.south) -- (stripe.north); - -\end{tikzpicture} - -\subsubsection{Backend-Paketstruktur} -Das Backend folgt dem Domain-Driven Design (DDD) Ansatz: - -\dirtree{% -.1 de.szut.casino. -.2 blackjack. -.3 BlackjackController.java. -.3 BlackjackService.java. -.3 BlackjackEntity.java. -.2 coinflip. -.3 CoinflipController.java. -.3 CoinflipService.java. -.2 config. -.3 SecurityConfig.java. -.3 DatabaseConfig.java. -.2 deposit. -.3 DepositController.java. -.3 StripeService.java. -.2 dice. -.3 DiceController.java. -.3 DiceService.java. -.2 exceptionHandling. -.3 GlobalExceptionHandler.java. -.2 health. -.3 HealthController.java. -.2 lootboxes. -.3 LootBoxEntity.java. -.3 RewardEntity.java. -.2 security. -.3 JwtAuthenticationConverter.java. -.2 shared. -.3 BaseEntity.java. -.3 AuditingConfig.java. -.2 slots. -.3 SlotsController.java. -.3 SlotsService.java. -.2 user. -.3 UserEntity.java. -.3 UserService.java. -} - -\subsubsection{Frontend-Modulstruktur} -Das Frontend ist modular aufgebaut mit Feature-Modulen: - -\dirtree{% -.1 app/feature/. -.2 auth. -.3 login. -.3 oauth2. -.3 recover-password. -.3 register. -.3 verify-email. -.2 deposit. -.3 deposit.component.ts. -.3 payment.service.ts. -.2 game. -.3 blackjack. -.4 components. -.5 animated-number. -.5 dealer-hand. -.5 game-controls. -.5 game-info. -.5 game-result. -.5 player-hand. -.5 playing-card. -.4 enum. -.4 models. -.4 services. -.3 coinflip. -.4 models. -.3 dice. -.3 slots. -.2 home. -.2 landing. -.2 lootboxes. -.3 lootbox-opening. -.3 lootbox-selection. -.3 services. -.2 transaction-history. -} - -\subsection{Funktionsweise des Blackjack-Algorithmus} -Als Beispiel für die Spiellogik wird hier der Blackjack-Algorithmus beschrieben: - -\begin{enumerate} - \item \textbf{Spielstart:} Dealer teilt je 2 Karten an Spieler und sich selbst aus - \item \textbf{Kartenwerte:} Ass = 1 oder 11, Bildkarten = 10, andere = Nennwert - \item \textbf{Spielerzug:} Hit (weitere Karte) oder Stand (beenden) - \item \textbf{Dealerzug:} Automatisch bis Kartenwert $\geq$ 17 - \item \textbf{Gewinnermittlung:} Nächster zu 21 gewinnt, bei Gleichstand Unentschieden -\end{enumerate} - -\section{Technologie-Stack} - -\subsection{Frontend-Technologien} -\begin{itemize} - \item \textbf{Angular 20}: Modernes TypeScript-basiertes Web-Framework - \item \textbf{TailwindCSS}: Utility-First CSS-Framework - \item \textbf{RxJS}: Reactive Extensions für asynchrone Programmierung - \item \textbf{Keycloak JS}: JavaScript-Adapter für Keycloak-Integration - \item \textbf{Stripe.js}: Sichere Payment-Integration -\end{itemize} - -\subsection{Backend-Technologien} -\begin{itemize} - \item \textbf{Spring Boot}: Java-basiertes Backend-Framework - \item \textbf{Spring Security}: Sicherheitsframework - \item \textbf{Spring Data JPA}: Datenbankzugriff - \item \textbf{PostgreSQL}: Relationale Datenbank - \item \textbf{Gradle}: Build-Management-Tool -\end{itemize} - -\subsection{Infrastructure \& Tools} -\begin{itemize} - \item \textbf{Docker}: Containerisierung - \item \textbf{Docker Compose}: Multi-Container-Orchestrierung - \item \textbf{Keycloak}: Identity und Access Management - \item \textbf{Mailpit}: E-Mail-Testing in der Entwicklung - \item \textbf{Swagger/OpenAPI}: API-Dokumentation -\end{itemize} - -\chapter{Implementierung} - -\section{Backend-Implementierung} - -\subsection{Spring Boot Konfiguration} -Die Hauptanwendungsklasse initialisiert die Spring Boot-Anwendung und konfiguriert grundlegende Beans: - -\begin{lstlisting}[language=Java, caption=CasinoApplication.java] -@SpringBootApplication -public class CasinoApplication { - public static void main(String[] args) { - SpringApplication.run(CasinoApplication.class, args); - } - - @Bean - public static RestTemplate restTemplate() { - return new RestTemplate(); - } - - @Bean - public static JavaMailSenderImpl javaMailSenderImpl() { - return new JavaMailSenderImpl(); - } -} -\end{lstlisting} - -\subsection{Sicherheitskonfiguration} -Die Anwendung nutzt Keycloak für die Authentifizierung. Spring Security wird entsprechend konfiguriert: - -\begin{lstlisting}[language=Java, caption=Security Configuration] -@Configuration -@EnableWebSecurity -public class SecurityConfig { - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) { - return http - .authorizeHttpRequests(auth -> auth - .requestMatchers("/swagger/**").permitAll() - .requestMatchers("/health/**").permitAll() - .anyRequest().authenticated() - ) - .oauth2ResourceServer(oauth2 -> - oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter( - jwtAuthenticationConverter() - )) - ) - .build(); - } -} -\end{lstlisting} - -\subsection{Spieleimplementierung} -Jedes Spiel folgt einem einheitlichen Muster mit Controller, Service und Repository: - -\begin{lstlisting}[language=Java, caption=Beispiel: Blackjack Controller] -@RestController -@RequestMapping("/api/blackjack") -@RequiredArgsConstructor -public class BlackjackController { - private final BlackjackService blackjackService; - - @PostMapping("/start") - public ResponseEntity startGame() { - return ResponseEntity.ok( - blackjackService.startNewGame() - ); - } - - @PostMapping("/hit") - public ResponseEntity hit() { - return ResponseEntity.ok( - blackjackService.playerHit() - ); - } - - @PostMapping("/stand") - public ResponseEntity stand() { - return ResponseEntity.ok( - blackjackService.playerStand() - ); - } -} -\end{lstlisting} - -\section{Frontend-Implementierung} - -\subsection{Angular-Projektstruktur} -Das Frontend nutzt Angular's modulare Architektur mit Lazy Loading: - -\begin{lstlisting}[caption=App Routing Module] -const routes: Routes = [ - { - path: '', - loadChildren: () => import('./feature/landing/landing.module') - .then(m => m.LandingModule) - }, - { - path: 'games', - loadChildren: () => import('./feature/game/game.module') - .then(m => m.GameModule), - canActivate: [AuthGuard] - }, - { - path: 'deposit', - loadChildren: () => import('./feature/deposit/deposit.module') - .then(m => m.DepositModule), - canActivate: [AuthGuard] - } -]; -\end{lstlisting} - -\subsection{Komponenten-Implementierung} -Jede Spielkomponente folgt dem Angular-Standard mit TypeScript: - -\begin{lstlisting}[caption=Blackjack Component] -@Component({ - selector: 'app-blackjack', - templateUrl: './blackjack.component.html', - styleUrls: ['./blackjack.component.scss'] -}) -export class BlackjackComponent implements OnInit { - gameState$: Observable; - - constructor( - private blackjackService: BlackjackService, - private toastr: ToastrService - ) {} - - ngOnInit(): void { - this.gameState$ = this.blackjackService.getGameState(); - } - - startGame(): void { - this.blackjackService.startGame() - .subscribe({ - next: () => this.toastr.success('Spiel gestartet!'), - error: (err) => this.toastr.error('Fehler beim Starten') - }); - } -} -\end{lstlisting} - -\subsection{Service-Layer} -Services kapseln die Geschäftslogik und API-Kommunikation: - -\begin{lstlisting}[caption=Blackjack Service] -@Injectable({ - providedIn: 'root' -}) -export class BlackjackService { - private apiUrl = '/api/blackjack'; - private gameStateSubject = new BehaviorSubject(null); - - constructor(private http: HttpClient) {} - - startGame(): Observable { - return this.http.post(`${this.apiUrl}/start`, {}) - .pipe( - tap(state => this.gameStateSubject.next(state)), - catchError(this.handleError) - ); - } - - private handleError(error: HttpErrorResponse): Observable { - console.error('API Error:', error); - return throwError(() => error); - } -} -\end{lstlisting} - -\section{Datenbankdesign} - -\subsection{Entity-Relationship-Modell} -Die Datenbank verwendet folgende Hauptentitäten: - -\begin{lstlisting}[caption=Haupttabellen] --- Benutzer-Tabelle -CREATE TABLE users ( - id UUID PRIMARY KEY, - keycloak_id VARCHAR(255) UNIQUE NOT NULL, - balance DECIMAL(19, 2) DEFAULT 0.00, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - --- Transaktions-Tabelle -CREATE TABLE transactions ( - id UUID PRIMARY KEY, - user_id UUID REFERENCES users(id), - amount DECIMAL(19, 2) NOT NULL, - type VARCHAR(50) NOT NULL, - description TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - --- Spielverlauf-Tabelle -CREATE TABLE game_history ( - id UUID PRIMARY KEY, - user_id UUID REFERENCES users(id), - game_type VARCHAR(50) NOT NULL, - bet_amount DECIMAL(19, 2), - win_amount DECIMAL(19, 2), - played_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); -\end{lstlisting} - -\subsection{JPA-Entitäten} -Die Datenbankentitäten werden als JPA-Entities modelliert: - -\begin{lstlisting}[caption=User Entity] -@Entity -@Table(name = "users") -@Data -@NoArgsConstructor -@AllArgsConstructor -public class UserEntity { - @Id - @GeneratedValue(strategy = GenerationType.UUID) - private UUID id; - - @Column(unique = true, nullable = false) - private String keycloakId; - - @Column(precision = 19, scale = 2) - private BigDecimal balance = BigDecimal.ZERO; - - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) - private List transactions; - - @CreationTimestamp - private LocalDateTime createdAt; - - @UpdateTimestamp - private LocalDateTime updatedAt; -} -\end{lstlisting} - -\chapter{Integration externer Dienste} - -\section{Keycloak-Integration} -Keycloak wird als zentraler Identity Provider verwendet: - -\subsection{Backend-Konfiguration} -\begin{lstlisting}[caption=application.yml] -spring: - security: - oauth2: - resourceserver: - jwt: - issuer-uri: http://localhost:8080/realms/casino - jwk-set-uri: http://localhost:8080/realms/casino/protocol/openid-connect/certs - -keycloak: - realm: casino - auth-server-url: http://localhost:8080 - ssl-required: external - resource: casino-backend - public-client: false - bearer-only: true -\end{lstlisting} - -\subsection{Frontend-Integration} -\begin{lstlisting}[caption=Keycloak Service] -@Injectable({ - providedIn: 'root' -}) -export class KeycloakService { - private keycloak: Keycloak; - - async init(): Promise { - this.keycloak = new Keycloak({ - url: 'http://localhost:8080', - realm: 'casino', - clientId: 'casino-frontend' - }); - - return await this.keycloak.init({ - onLoad: 'check-sso', - silentCheckSsoRedirectUri: - window.location.origin + '/assets/silent-check-sso.html' - }); - } -} -\end{lstlisting} - -\section{Stripe-Integration} -Stripe wird für sichere Zahlungsabwicklung verwendet: - -\subsection{Backend-Webhook} -\begin{lstlisting}[caption=Stripe Webhook Controller] -@RestController -@RequestMapping("/webhook") -public class StripeWebhookController { - @Value("${stripe.webhook.secret}") - private String webhookSecret; - - @PostMapping - public ResponseEntity handleWebhook( - @RequestBody String payload, - @RequestHeader("Stripe-Signature") String sigHeader - ) { - Event event = Webhook.constructEvent( - payload, sigHeader, webhookSecret - ); - - switch (event.getType()) { - case "payment_intent.succeeded": - handleSuccessfulPayment(event); - break; - case "payment_intent.payment_failed": - handleFailedPayment(event); - break; - } - - return ResponseEntity.ok("Received"); - } -} -\end{lstlisting} - -\subsection{Frontend-Integration} -\begin{lstlisting}[caption=Stripe Service] -@Injectable({ - providedIn: 'root' -}) -export class StripeService { - private stripe: Stripe; - - constructor() { - this.stripe = loadStripe(environment.stripePublicKey); - } - - async createPaymentIntent(amount: number): Observable { - return this.http.post('/api/deposits/create-intent', { - amount: amount * 100 // Cent-Betrag - }); - } -} -\end{lstlisting} - -\chapter{Qualitätssicherung} - -\section{CI/CD Setup} - -\subsection{Unit-Tests} -Jede Service-Klasse wird mit JUnit und Mockito getestet: - -\begin{lstlisting}[language=Java, caption=BlackjackService Test] -@ExtendWith(MockitoExtension.class) -class BlackjackServiceTest { - @Mock - private BlackjackRepository repository; - - @InjectMocks - private BlackjackService service; - - @Test - void testStartNewGame() { - // Given - when(repository.save(any())).thenReturn(new GameEntity()); - - // When - GameStateDto result = service.startNewGame(); - - // Then - assertNotNull(result); - assertEquals(2, result.getPlayerCards().size()); - assertEquals(2, result.getDealerCards().size()); - } -} -\end{lstlisting} - -\subsection{Integrationstests} -Spring Boot Test für API-Endpoints: - -\begin{lstlisting}[language=Java, caption=Integration Test] -@SpringBootTest -@AutoConfigureMockMvc -class BlackjackControllerIntegrationTest { - @Autowired - private MockMvc mockMvc; - - @Test - @WithMockUser - void testStartGameEndpoint() throws Exception { - mockMvc.perform(post("/api/blackjack/start") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.playerCards").isArray()) - .andExpect(jsonPath("$.dealerCards").isArray()); - } -} -\end{lstlisting} - -\section{Frontend-Tests} - -\subsection{Komponenten-Tests} -Angular-Komponenten werden mit Jasmine und Karma getestet: - -\begin{lstlisting}[caption=Component Test] -describe('BlackjackComponent', () => { - let component: BlackjackComponent; - let fixture: ComponentFixture; - let mockService: jasmine.SpyObj; - - beforeEach(() => { - const spy = jasmine.createSpyObj('BlackjackService', - ['startGame', 'getGameState']); - - TestBed.configureTestingModule({ - declarations: [BlackjackComponent], - providers: [ - { provide: BlackjackService, useValue: spy } - ] - }); - - fixture = TestBed.createComponent(BlackjackComponent); - component = fixture.componentInstance; - mockService = TestBed.inject(BlackjackService) as any; - }); - - it('should start a new game', () => { - mockService.startGame.and.returnValue(of(mockGameState)); - - component.startGame(); - - expect(mockService.startGame).toHaveBeenCalled(); - }); -}); -\end{lstlisting} - -\subsection{E2E-Tests} -End-to-End-Tests mit Cypress: - -\begin{lstlisting}[caption=E2E Test] -describe('Blackjack Game', () => { - beforeEach(() => { - cy.login('testuser', 'password'); - cy.visit('/games/blackjack'); - }); - - it('should play a complete game', () => { - cy.get('[data-cy=start-game]').click(); - cy.get('[data-cy=player-cards]').should('be.visible'); - cy.get('[data-cy=dealer-cards]').should('be.visible'); - - cy.get('[data-cy=hit-button]').click(); - cy.get('[data-cy=player-cards]') - .children().should('have.length.greaterThan', 2); - - cy.get('[data-cy=stand-button]').click(); - cy.get('[data-cy=game-result]').should('be.visible'); - }); -}); -\end{lstlisting} - -\chapter{Wirtschaftliche Betrachtung} - -\section{Marktuntersuchung} - -\subsection{Zielgruppe} -\textbf{Welche Zielgruppe soll angesprochen werden?} - -\begin{itemize} - \item \textbf{Primäre Zielgruppe:} Technik-affine Nutzer (18-35 Jahre) - \item \textbf{Sekundäre Zielgruppe:} Gelegenheitsspieler mit Fokus auf Benutzerfreundlichkeit -\end{itemize} - -\subsection{Erwartungen der Zielgruppe} -\textbf{Welche Erwartungen hat die Zielgruppe an das Produkt?} - -Die Zielgruppe erwartet ein unterhaltsames, fesselndes und optisch ansprechendes Spielerlebnis, das klassische Casino-Atmosphäre simuliert. Sie legt Wert auf: - -\begin{itemize} - \item Intuitive Benutzeroberfläche - \item Schnelle Ladezeiten - \item Abwechslungsreiche Spiele (Slots, Blackjack, CoinFlip etc.) - \item Transparente Transaktionshistorie - \item Mobile Optimierung - \item Sichere Zahlungsabwicklung -\end{itemize} - -\subsection{Marktvolumen und Marktpotential} -\textbf{Wie hoch ist bei der Zielgruppe das Marktvolumen und das Marktpotential?} - -Der Markt für Social Casinos und Online-Casinos mit Auszahlungsverweigerung ist weltweit wachsend. Laut Branchenanalysen (z.B. Statista, Business Research Insights) liegt das Volumen des globalen Social-Casino-Markts bei mehreren Milliarden USD pro Jahr, mit jährlichen Wachstumsraten im hohen einstelligen Prozentbereich. - -In Zielmärkten wie den USA, Europa und Teilen Asiens ist das Potenzial besonders hoch, insbesondere bei Nutzern, die Glücksspiel aus rechtlichen oder moralischen Gründen meiden, aber den Spielspaß schätzen. - -\subsection{Absatzpotential} -\textbf{Wie hoch ist bei der Zielgruppe das Absatzpotential?} - -Das Absatzpotential ist aufgrund des digitalen Charakters des Produkts nahezu unbegrenzt skalierbar. Wichtige Faktoren: - -\begin{itemize} - \item Keine physischen Produktionslimits - \item Globale Erreichbarkeit über das Internet - \item Virale Marketing-Effekte durch Social Gaming - \item Wiederkehrende Umsätze durch Einzahlungsmodell -\end{itemize} - -\subsection{Wettbewerbssituation} -\textbf{Wie stellt sich die Wettbewerbssituation dar?} - -Der Markt ist stark umkämpft mit etablierten Playern wie: -\begin{itemize} - \item PokerStars Casino - \item Slotomania - \item House of Fun - \item Big Fish Casino -\end{itemize} - -\textbf{Konkurrenzvorteile unserer Lösung:} -\begin{itemize} - \item Moderne Technologie-Stack (Angular + Spring Boot) - \item DSGVO-konforme Entwicklung von Grund auf - \item Keine Auszahlungen = höhere Gewinnmargen - \item Responsive Design für alle Endgeräte - \item Open-Source-Ansatz für Community-Entwicklung -\end{itemize} - -\subsection{Risiken} -\textbf{Wo liegen Risiken?} - -\begin{itemize} - \item \textbf{Rechtliche Risiken:} Sich ändernde Glücksspielgesetze - \item \textbf{Technische Risiken:} Skalierungsprobleme bei hohen Nutzerzahlen - \item \textbf{Marktrisiken:} Marktsättigung im Social Gaming Bereich - \item \textbf{Sicherheitsrisiken:} Hacking-Versuche und Betrug -\end{itemize} - -\section{Marketingmix / absatzpolitische Maßnahmen} - -\subsection{Preispolitik} -\textbf{Für welchen Preis wird das Produkt angeboten?} - -Das Produkt folgt einem Freemium-Modell: -\begin{itemize} - \item \textbf{Grundspiel:} Kostenlos mit Startguthaben - \item \textbf{Einzahlungen:} Gestaffelte Pakete (5€, 10€, 25€, 50€, 100€) - \item \textbf{Premium-Features:} Erweiterte Statistiken (2,99€/Monat) -\end{itemize} - -\subsection{Kommunikationspolitik} -\textbf{Wie und wo soll das Produkt beworben werden?} - -\begin{itemize} - \item \textbf{Social Media Marketing:} Instagram, TikTok, YouTube - \item \textbf{Influencer Marketing:} Gaming-Streamer und Content-Creator - \item \textbf{SEO/SEM:} Google Ads für relevante Suchbegriffe - \item \textbf{App Store Optimization:} Bei zukünftiger Mobile App -\end{itemize} - -\subsection{Distributionspolitik} -\textbf{Wie gelangt das Produkt zum Nutzer?} - -\begin{itemize} - \item \textbf{Web-Browser:} Direkter Zugang über casino-platform.com - \item \textbf{Progressive Web App:} Installation als App möglich - \item \textbf{Zukünftig:} Native Apps für iOS und Android -\end{itemize} - -\subsection{Wirtschaftlichkeitsbetrachtung} -\textbf{Wo liegt die Gewinnschwelle?} - -Bei durchschnittlich 10€ Einzahlung pro aktivem Nutzer und 1000 aktiven Nutzern monatlich: -\begin{itemize} - \item \textbf{Monatlicher Umsatz:} 10.000€ - \item \textbf{Betriebskosten:} 3.000€ (Server, Support, Marketing) - \item \textbf{Break-Even:} Nach 6 Monaten bei stetigem Nutzerwachstum -\end{itemize} - -\chapter{Deployment und Betrieb} - -\section{Container-Konfiguration} -Die Anwendung wird mit Docker containerisiert: - -\subsection{Backend Dockerfile} -\begin{lstlisting}[caption=Backend Dockerfile] -FROM eclipse-temurin:17-jdk-alpine AS build -WORKDIR /app -COPY gradle gradle -COPY gradlew build.gradle.kts settings.gradle.kts ./ -RUN ./gradlew dependencies --no-daemon -COPY src src -RUN ./gradlew bootJar --no-daemon - -FROM eclipse-temurin:17-jre-alpine -WORKDIR /app -COPY --from=build /app/build/libs/*.jar app.jar -EXPOSE 8080 -ENTRYPOINT ["java", "-jar", "app.jar"] -\end{lstlisting} - -\subsection{Frontend Dockerfile} -\begin{lstlisting}[caption=Frontend Dockerfile] -FROM node:18-alpine AS build -WORKDIR /app -COPY package*.json ./ -RUN npm ci -COPY . . -RUN npm run build - -FROM nginx:alpine -COPY --from=build /app/dist/casino-frontend /usr/share/nginx/html -COPY nginx.conf /etc/nginx/nginx.conf -EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] -\end{lstlisting} - -\section{CI/CD-Pipeline} -Die Anwendung nutzt GitHub Actions für automatisierte Builds: - -\begin{lstlisting}[caption=GitHub Actions Workflow] -name: Build and Deploy - -on: - push: - branches: [main, develop] - pull_request: - branches: [main] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: '17' - - - name: Run Backend Tests - working-directory: ./backend - run: ./gradlew test - - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: '18' - - - name: Run Frontend Tests - working-directory: ./frontend - run: | - npm ci - npm run test:ci - - build: - needs: test - runs-on: ubuntu-latest - steps: - - name: Build Docker Images - run: | - docker build -t casino-backend ./backend - docker build -t casino-frontend ./frontend -\end{lstlisting} - -\section{Monitoring und Wartung} - -\subsection{Health Checks} -Spring Boot Actuator bietet Health-Endpoints: - -\begin{lstlisting}[language=Java, caption=Health Check Configuration] -@Component -public class DatabaseHealthIndicator implements HealthIndicator { - @Autowired - private DataSource dataSource; - - @Override - public Health health() { - try (Connection conn = dataSource.getConnection()) { - if (conn.isValid(1)) { - return Health.up() - .withDetail("database", "PostgreSQL") - .build(); - } - } catch (SQLException e) { - return Health.down() - .withException(e) - .build(); - } - return Health.down().build(); - } -} -\end{lstlisting} - -\subsection{Logging-Konfiguration} -Strukturiertes Logging mit SLF4J: - -\begin{lstlisting}[language=xml, caption=logback.xml] - - - - - %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n - - - - - - logs/casino.log - - logs/casino.%d{yyyy-MM-dd}.log - 30 - - - - %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - -\end{lstlisting} - -\chapter{Sicherheitskonzept} - -\section{Authentifizierung und Autorisierung} -Die Plattform implementiert ein mehrstufiges Sicherheitskonzept: - -\subsection{OAuth2/OpenID Connect} -Keycloak als Identity Provider bietet: -\begin{itemize} - \item Single Sign-On (SSO) - \item Multi-Factor Authentication (MFA) - \item Social Login (Google, GitHub) - \item Token-basierte Authentifizierung (JWT) -\end{itemize} - -\subsection{API-Sicherheit} -\begin{lstlisting}[language=Java, caption=Security Headers] -@Component -public class SecurityHeadersFilter implements Filter { - @Override - public void doFilter(ServletRequest request, - ServletResponse response, - FilterChain chain) { - HttpServletResponse httpResponse = - (HttpServletResponse) response; - - httpResponse.setHeader("X-Content-Type-Options", "nosniff"); - httpResponse.setHeader("X-Frame-Options", "DENY"); - httpResponse.setHeader("X-XSS-Protection", "1; mode=block"); - httpResponse.setHeader("Strict-Transport-Security", - "max-age=31536000; includeSubDomains"); - - chain.doFilter(request, response); - } -} -\end{lstlisting} - -\section{Datenschutz} -Die Anwendung erfüllt DSGVO-Anforderungen: - -\begin{itemize} - \item Verschlüsselte Datenspeicherung - \item Recht auf Datenlöschung - \item Datenportabilität - \item Audit-Logging aller Zugriffe - \item Cookie-Consent-Management -\end{itemize} - -\section{Zahlungssicherheit} -PCI-DSS-Compliance durch: -\begin{itemize} - \item Keine Speicherung von Kreditkartendaten - \item Stripe-Integration für Zahlungsverarbeitung - \item TLS-Verschlüsselung aller Transaktionen - \item Webhook-Validierung mit Signatur -\end{itemize} - -\chapter{Abschluss} - -\section{Projektziel} - -\subsection{Wurde das Projektziel erreicht?} -Das Projektziel wurde erfolgreich erreicht. Die Casino Gaming Platform wurde innerhalb der vorgegebenen 70 Stunden vollständig entwickelt und implementiert. - -\subsubsection{Soll-Ist Vergleich} -\begin{longtable}{|p{6cm}|p{3cm}|p{6cm}|} -\hline -\textbf{Anforderung} & \textbf{Status} & \textbf{Bemerkung} \\ -\hline -5 Casino-Spiele & \ding{51} Erreicht & Blackjack, Slots, Dice, Coinflip, Lootboxes \\ -\hline -OAuth2-Authentifizierung & \ding{51} Erreicht & GitHub und Google Integration \\ -\hline -Stripe-Zahlungssystem & \ding{51} Erreicht & Vollständig funktionstüchtig \\ -\hline -Responsive Design & \ding{51} Erreicht & Mobile-First Ansatz \\ -\hline -Transaktionshistorie & \ding{51} Erreicht & Vollständige Nachverfolgung \\ -\hline -DSGVO-Konformität & \ding{51} Erreicht & Datenschutz implementiert \\ -\hline -API-Dokumentation & \ding{51} Erreicht & Swagger/OpenAPI \\ -\hline -\end{longtable} - -\subsection{Änderungen zur anfänglichen Planung} -\begin{itemize} - \item \textbf{Poker-Spiel:} Wurde auf zukünftige Version verschoben aufgrund der Komplexität - \item \textbf{WebSocket-Integration:} Zusätzlich implementiert für Real-time Updates - \item \textbf{Docker-Containerisierung:} Früher als geplant eingeführt für bessere Entwicklungsumgebung -\end{itemize} - -\subsection{Einhaltung des zeitlichen Rahmens} -Der zeitliche Rahmen von 70 Stunden wurde eingehalten: - -\begin{itemize} - \item \textbf{Analyse \& Design:} 15 Stunden (geplant: 20 Stunden) - \item \textbf{Backend-Entwicklung:} 25 Stunden (geplant: 25 Stunden) - \item \textbf{Frontend-Entwicklung:} 20 Stunden (geplant: 15 Stunden) - \item \textbf{Testing \& QA:} 8 Stunden (geplant: 8 Stunden) - \item \textbf{Dokumentation:} 2 Stunden (geplant: 2 Stunden) -\end{itemize} - -\section{Fazit} - -\subsection{Teamarbeit} -\textbf{Wie hat die Teamarbeit funktioniert?} - -\begin{itemize} - \item \textbf{Positive Aspekte:} - \begin{itemize} - \item Gute Kommunikation über Scrum Daily Standups - \item Klare Aufgabenverteilung durch Jira-Tickets - \item Effektive Code-Reviews über Pull Requests - \item Konstruktive Problemlösungen im Team - \end{itemize} - - \item \textbf{Verbesserungspotential:} - \begin{itemize} - \item Besseres Ticket-Handling in der Anfangsphase - \item Weniger informelle Gespräche während Arbeitszeit - \item Frühzeitigere Integration von Komponenten - \end{itemize} -\end{itemize} - -\subsection{Persönlicher Lerngewinn} -\textbf{Was hat das Projekt mir persönlich gebracht?} - -\begin{itemize} - \item \textbf{Technische Kompetenzen:} - \begin{itemize} - \item Vertiefung in Angular 20 und moderne Frontend-Entwicklung - \item Praktische Erfahrung mit Spring Boot und Microservices - \item OAuth2 und JWT-basierte Authentifizierung - \item Container-Technologien (Docker, Docker Compose) - \item CI/CD-Pipelines mit GitHub Actions - \end{itemize} - - \item \textbf{Methodische Kompetenzen:} - \begin{itemize} - \item Agile Softwareentwicklung mit Scrum - \item Test-Driven Development (TDD) - \item Domain-Driven Design (DDD) - \item Clean Code Prinzipien - \end{itemize} - - \item \textbf{Projektmanagement:} - \begin{itemize} - \item Zeitmanagement bei komplexen Projekten - \item Risikomanagement und Problemlösung - \item Stakeholder-Kommunikation - \item Dokumentation und Präsentation - \end{itemize} -\end{itemize} - -\subsection{Ausblick} -Die entwickelte Casino Gaming Platform bietet eine solide Grundlage für weitere Entwicklungen. Die moderne Architektur und der saubere Code ermöglichen es, das System kontinuierlich zu erweitern und zu verbessern. - -\subsubsection{Geplante Erweiterungen} -\begin{itemize} - \item \textbf{Kurzfristig (3 Monate):} Mobile Apps für iOS und Android - \item \textbf{Mittelfristig (6 Monate):} Multiplayer-Funktionen und Turniere - \item \textbf{Langfristig (12 Monate):} KI-basierte Spielempfehlungen und erweiterte Analytics -\end{itemize} - -Das Projekt hat gezeigt, dass mit modernen Technologien und agilen Methoden auch komplexe Anwendungen effizient und qualitativ hochwertig entwickelt werden können. - -\appendix -\chapter{Anhang} - -\section{Glossar} -\begin{description} - \item[API] Application Programming Interface - Schnittstelle zur Kommunikation zwischen Softwarekomponenten - \item[CI/CD] Continuous Integration/Continuous Deployment - Automatisierte Build- und Deployment-Prozesse - \item[DDD] Domain-Driven Design - Softwaredesign-Ansatz - \item[DSGVO] Datenschutz-Grundverordnung - \item[JWT] JSON Web Token - Standard für sichere Informationsübertragung - \item[OAuth2] Autorisierungsframework für sichere API-Zugriffe - \item[PCI-DSS] Payment Card Industry Data Security Standard - \item[REST] Representational State Transfer - Architekturstil für Web-Services - \item[SSO] Single Sign-On - Einmalige Anmeldung für mehrere Dienste -\end{description} - -\section{Literaturverzeichnis} -\begin{enumerate} - \item Spring Boot Reference Documentation, Version 3.2, Spring Framework Team, 2024 - \item Angular Developer Guide, Version 20, Angular Team, Google, 2024 - \item Keycloak Server Administration Guide, Red Hat, 2024 - \item Stripe API Reference, Stripe Inc., 2024 - \item PostgreSQL Documentation, Version 17, PostgreSQL Global Development Group, 2024 - \item Docker Documentation, Docker Inc., 2024 - \item Clean Architecture, Robert C. Martin, Prentice Hall, 2017 - \item Domain-Driven Design, Eric Evans, Addison-Wesley, 2003 -\end{enumerate} - -\section{Abbildungsverzeichnis} -\begin{enumerate} - \item Systemarchitektur-Diagramm - \item ER-Diagramm der Datenbank - \item Sequenzdiagramm Authentifizierung - \item Klassendiagramm Backend - \item Komponentendiagramm Frontend -\end{enumerate} - -\section{Tabellenverzeichnis} -\begin{enumerate} - \item Projektphasen und Zeitaufwand - \item Technologie-Stack Übersicht - \item API-Endpoints Dokumentation - \item Testabdeckung pro Modul -\end{enumerate} - -\end{document} \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore index cc7b141..b93e819 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -29,6 +29,9 @@ yarn-error.log .history/* # Miscellaneous +/.claude +/test-results +/playwright-report /.angular/cache .sass-cache/ /connect.lock diff --git a/frontend/bun.lock b/frontend/bun.lock index 048e212..73270bf 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -32,10 +32,11 @@ "@angular-devkit/build-angular": "^20.0.0", "@angular/cli": "^20.0.0", "@angular/compiler-cli": "^20.0.0", + "@playwright/test": "^1.52.0", "@types/jasmine": "~5.1.0", - "angular-eslint": "19.7.1", + "angular-eslint": "20.0.0", "eslint": "^9.28.0", - "jasmine-core": "~5.7.0", + "jasmine-core": "~5.8.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", "karma-coverage": "~2.2.0", @@ -43,7 +44,7 @@ "karma-jasmine-html-reporter": "~2.1.0", "prettier": "^3.4.2", "typescript": "~5.8.0", - "typescript-eslint": "8.33.1", + "typescript-eslint": "8.34.0", }, }, }, @@ -62,19 +63,19 @@ "@angular-devkit/schematics": ["@angular-devkit/schematics@20.0.0", "", { "dependencies": { "@angular-devkit/core": "20.0.0", "jsonc-parser": "3.3.1", "magic-string": "0.30.17", "ora": "8.2.0", "rxjs": "7.8.2" } }, "sha512-35WbWP8ARnaqVjOzy7IOyWsY/jeyUqfVj4KgHG2O4fHAhIhaBqhP8dDDP+SwM+bToIqklg0fzHUUhFTRxzzyoQ=="], - "@angular-eslint/builder": ["@angular-eslint/builder@19.7.1", "", { "dependencies": { "@angular-devkit/architect": ">= 0.1900.0 < 0.2000.0", "@angular-devkit/core": ">= 19.0.0 < 20.0.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "sha512-11zk4JngV4KgnDNtaiYzx0EhwMFdSbZjSF+CMHN4yB90e8vukhQDg/L8PEdSUEnkBKMymK9xN1JLIE+n6ESPaQ=="], + "@angular-eslint/builder": ["@angular-eslint/builder@20.0.0", "", { "dependencies": { "@angular-devkit/architect": ">= 0.2000.0 < 0.2100.0", "@angular-devkit/core": ">= 20.0.0 < 21.0.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "sha512-9jS3VvY+K+EHw9pofsdwKxDirKuTuRBnjMZdaKoUfLoYy5eS1XGJBXoMdaQiM+mSlTv113+L0SK4U565xiBLHQ=="], - "@angular-eslint/bundled-angular-compiler": ["@angular-eslint/bundled-angular-compiler@19.7.1", "", {}, "sha512-L2xpEbyT3EJQ/dTmPntBu6MBeEG+3SKARJ9xuA4nnm6n7nn9EARmUmaGlBQZ2DFMOFeKcqR802gdPi8vCADXRA=="], + "@angular-eslint/bundled-angular-compiler": ["@angular-eslint/bundled-angular-compiler@20.0.0", "", {}, "sha512-mDXMQd08s11q9fC6Ps3ffZmvXop9eLuAAXexofHhA7uuoQAoUWS2zoOSNTWtDR6oxMcqEeMnALCjjFeJVBSVmg=="], - "@angular-eslint/eslint-plugin": ["@angular-eslint/eslint-plugin@19.7.1", "", { "dependencies": { "@angular-eslint/bundled-angular-compiler": "19.7.1", "@angular-eslint/utils": "19.7.1" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "sha512-VdNES8AxOHpSKTBfEkUtlvm/EV4BlpvWvMK2T0Eu2y2oHjxtBB6V4vYZR0PlSWS9IrE1MPfuH1htHPovBxiEdw=="], + "@angular-eslint/eslint-plugin": ["@angular-eslint/eslint-plugin@20.0.0", "", { "dependencies": { "@angular-eslint/bundled-angular-compiler": "20.0.0", "@angular-eslint/utils": "20.0.0" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "sha512-xzaLj2yEn43DH0bE9Gw3GrmC+jivIS5/Hbh3bDj3ctw3mUUrD8hrS7kBo1neZ0gnoVLoo/mwIldG+xs5NDY66A=="], - "@angular-eslint/eslint-plugin-template": ["@angular-eslint/eslint-plugin-template@19.7.1", "", { "dependencies": { "@angular-eslint/bundled-angular-compiler": "19.7.1", "@angular-eslint/utils": "19.7.1", "aria-query": "5.3.2", "axobject-query": "4.1.0" }, "peerDependencies": { "@angular-eslint/template-parser": "19.7.1", "@typescript-eslint/types": "^7.11.0 || ^8.0.0", "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "sha512-VQMlNF29Xbff6KMkUBWHbUyl0tIos1yI//q+ha+DCNYD7l41uoKnRIJ9c5vehXdgsqHWm4XBCVB3HKKMbrabrg=="], + "@angular-eslint/eslint-plugin-template": ["@angular-eslint/eslint-plugin-template@20.0.0", "", { "dependencies": { "@angular-eslint/bundled-angular-compiler": "20.0.0", "@angular-eslint/utils": "20.0.0", "aria-query": "5.3.2", "axobject-query": "4.1.0" }, "peerDependencies": { "@angular-eslint/template-parser": "20.0.0", "@typescript-eslint/types": "^7.11.0 || ^8.0.0", "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "sha512-QoGgrawU5JFcaj0TjXHKC6fiZkxBeGVRj/TWJtTo/x+c5TVoV5k9pI7Uxdmo9kr4SkPXmt80ZklvExSA510gyw=="], - "@angular-eslint/schematics": ["@angular-eslint/schematics@19.7.1", "", { "dependencies": { "@angular-devkit/core": ">= 19.0.0 < 20.0.0", "@angular-devkit/schematics": ">= 19.0.0 < 20.0.0", "@angular-eslint/eslint-plugin": "19.7.1", "@angular-eslint/eslint-plugin-template": "19.7.1", "ignore": "7.0.5", "semver": "7.7.2", "strip-json-comments": "3.1.1" } }, "sha512-IlfzqpAx+491G0yCa+qaqsAbPWZxRywfNc4SGjIGgze8Nal0PbTy8VLZgAJ7/u/rEUowSvt3oKytb9rGS5u9kw=="], + "@angular-eslint/schematics": ["@angular-eslint/schematics@20.0.0", "", { "dependencies": { "@angular-devkit/core": ">= 20.0.0 < 21.0.0", "@angular-devkit/schematics": ">= 20.0.0 < 21.0.0", "@angular-eslint/eslint-plugin": "20.0.0", "@angular-eslint/eslint-plugin-template": "20.0.0", "ignore": "7.0.5", "semver": "7.7.2", "strip-json-comments": "3.1.1" } }, "sha512-VL3Sb6Df+iiUSPaQG8NxMPLx0dFRtRGSzsfe6CWYW7FUFP5dYEjpB63gKSAiIBLjPgnG6PMAzrRtfN4nDaTM+g=="], - "@angular-eslint/template-parser": ["@angular-eslint/template-parser@19.7.1", "", { "dependencies": { "@angular-eslint/bundled-angular-compiler": "19.7.1", "eslint-scope": "^8.0.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "sha512-SEuOjQJBTOOJ2DK0Wfiu1nO1F+fkXVehIk8xabEKZBY+t2mqbAiwBeWZsCSm4xx4BE689FwCHjVEF2c0yLvq1A=="], + "@angular-eslint/template-parser": ["@angular-eslint/template-parser@20.0.0", "", { "dependencies": { "@angular-eslint/bundled-angular-compiler": "20.0.0", "eslint-scope": "^8.0.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "sha512-5y9hxH/z+9rIOJp1FwRBSgJ6xt8/pgRfBF+eEIPyIHKl5mV0cVzlQiD7j1LMYTcxJZLHAoryomvSBDpmbtAlWg=="], - "@angular-eslint/utils": ["@angular-eslint/utils@19.7.1", "", { "dependencies": { "@angular-eslint/bundled-angular-compiler": "19.7.1" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "sha512-iNfAwnQFBwNwh64BnTIdypFnBwyjjxtdEtAwNxM0u5ApX2MAu+koHdwGiXvyeJPq+g7stXwVpuvaAnHlxTzcig=="], + "@angular-eslint/utils": ["@angular-eslint/utils@20.0.0", "", { "dependencies": { "@angular-eslint/bundled-angular-compiler": "20.0.0" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "sha512-3wsx0iX5f/IQgcTwXIzQq2VPHSjYXJasKNSfgMyKXn4MJGljaSNj+A0ao/5zjnwWVpL0vK5PQsk7EIuMcgAdrg=="], "@angular/animations": ["@angular/animations@20.0.0", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/common": "20.0.0", "@angular/core": "20.0.0" } }, "sha512-yU4hUH6AheY0dnMSaLRMfgnXhg/JUSUvrhE+lHzIiSKdEf0lyo1Ri6bkPD1CbamxZ94BqhRNCApvbvTbibGICQ=="], @@ -542,6 +543,8 @@ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@playwright/test": ["@playwright/test@1.52.0", "", { "dependencies": { "playwright": "1.52.0" }, "bin": { "playwright": "cli.js" } }, "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.40.2", "", { "os": "android", "cpu": "arm" }, "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.40.2", "", { "os": "android", "cpu": "arm64" }, "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw=="], @@ -684,9 +687,9 @@ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.33.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.33.1", "@typescript-eslint/type-utils": "8.33.1", "@typescript-eslint/utils": "8.33.1", "@typescript-eslint/visitor-keys": "8.33.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.33.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.34.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.34.0", "@typescript-eslint/type-utils": "8.34.0", "@typescript-eslint/utils": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.34.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w=="], - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.33.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.33.1", "@typescript-eslint/types": "8.33.1", "@typescript-eslint/typescript-estree": "8.33.1", "@typescript-eslint/visitor-keys": "8.33.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA=="], + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.34.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.34.0", "@typescript-eslint/types": "8.34.0", "@typescript-eslint/typescript-estree": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA=="], "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.33.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.33.1", "@typescript-eslint/types": "^8.33.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw=="], @@ -694,7 +697,7 @@ "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.33.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g=="], - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.33.1", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.33.1", "@typescript-eslint/utils": "8.33.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww=="], + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.34.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.34.0", "@typescript-eslint/utils": "8.34.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg=="], "@typescript-eslint/types": ["@typescript-eslint/types@8.33.1", "", {}, "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg=="], @@ -702,7 +705,7 @@ "@typescript-eslint/utils": ["@typescript-eslint/utils@8.33.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.33.1", "@typescript-eslint/types": "8.33.1", "@typescript-eslint/typescript-estree": "8.33.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.33.1", "", { "dependencies": { "@typescript-eslint/types": "8.33.1", "eslint-visitor-keys": "^4.2.0" } }, "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.34.0", "", { "dependencies": { "@typescript-eslint/types": "8.34.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA=="], "@vitejs/plugin-basic-ssl": ["@vitejs/plugin-basic-ssl@2.0.0", "", { "peerDependencies": { "vite": "^6.0.0" } }, "sha512-gc9Tjg8bUxBVSTzeWT3Njc0Cl3PakHFKdNfABnZWiUgbxqmHDEn7uECv3fHVylxoYgNzAcmU7ZrILz+BwSo3sA=="], @@ -760,7 +763,7 @@ "ajv-keywords": ["ajv-keywords@5.1.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "ajv": "^8.8.2" } }, "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw=="], - "angular-eslint": ["angular-eslint@19.7.1", "", { "dependencies": { "@angular-devkit/core": ">= 19.0.0 < 20.0.0", "@angular-devkit/schematics": ">= 19.0.0 < 20.0.0", "@angular-eslint/builder": "19.7.1", "@angular-eslint/eslint-plugin": "19.7.1", "@angular-eslint/eslint-plugin-template": "19.7.1", "@angular-eslint/schematics": "19.7.1", "@angular-eslint/template-parser": "19.7.1", "@typescript-eslint/types": "^8.0.0", "@typescript-eslint/utils": "^8.0.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "*", "typescript-eslint": "^8.0.0" } }, "sha512-xSjDf3Tdc7gA99Uk4sOfP/3I2jvKlCvuFnqWxw2mzByVNAgT9QUTY51+Z0jFT8JOnsN+UwE3fDPOHuSu183Mew=="], + "angular-eslint": ["angular-eslint@20.0.0", "", { "dependencies": { "@angular-devkit/core": ">= 20.0.0 < 21.0.0", "@angular-devkit/schematics": ">= 20.0.0 < 21.0.0", "@angular-eslint/builder": "20.0.0", "@angular-eslint/eslint-plugin": "20.0.0", "@angular-eslint/eslint-plugin-template": "20.0.0", "@angular-eslint/schematics": "20.0.0", "@angular-eslint/template-parser": "20.0.0", "@typescript-eslint/types": "^8.0.0", "@typescript-eslint/utils": "^8.0.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "*", "typescript-eslint": "^8.0.0" } }, "sha512-9wCkzR+oxMKDXktFItI10dFaX4qCuz9SgClXdh/ZHmCANHK/RtPnXnD+gROPvhNN1M6BAJKialjIrs88orz97A=="], "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], @@ -794,8 +797,6 @@ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - "base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="], "batch": ["batch@0.6.1", "", {}, "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw=="], @@ -806,8 +807,6 @@ "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], - "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], - "body-parser": ["body-parser@1.20.3", "", { "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" } }, "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g=="], "bonjour-service": ["bonjour-service@1.3.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA=="], @@ -820,8 +819,6 @@ "browserslist": ["browserslist@4.25.0", "", { "dependencies": { "caniuse-lite": "^1.0.30001718", "electron-to-chromium": "^1.5.160", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA=="], - "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], - "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], @@ -858,8 +855,6 @@ "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], - "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="], - "clone-deep": ["clone-deep@4.0.1", "", { "dependencies": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", "shallow-clone": "^3.0.0" } }, "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ=="], "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], @@ -926,8 +921,6 @@ "default-browser-id": ["default-browser-id@5.0.0", "", {}, "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA=="], - "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="], - "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], @@ -1082,7 +1075,7 @@ "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], @@ -1152,8 +1145,6 @@ "icss-utils": ["icss-utils@5.1.0", "", { "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA=="], - "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "ignore-walk": ["ignore-walk@7.0.0", "", { "dependencies": { "minimatch": "^9.0.0" } }, "sha512-T4gbf83A4NH95zvhVYZc+qWocBBGlpzUXLPGurJggw/WIOwicfXJChLDP/iBZnN5WqROSu5Bm3hhle4z8a8YGQ=="], @@ -1230,7 +1221,7 @@ "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - "jasmine-core": ["jasmine-core@5.7.1", "", {}, "sha512-QnurrtpKsPoixxG2R3d1xP0St/2kcX5oTZyDyQJMY+Vzi/HUlu1kGm+2V8Tz+9lV991leB1l0xcsyz40s9xOOw=="], + "jasmine-core": ["jasmine-core@5.8.0", "", {}, "sha512-Q9dqmpUAfptwyueW3+HqBOkSuYd9I/clZSSfN97wXE/Nr2ROFNCwIBEC1F6kb3QXS9Fcz0LjFYSDQT+BiwjuhA=="], "jest-worker": ["jest-worker@27.5.1", "", { "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg=="], @@ -1362,8 +1353,6 @@ "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], - "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], "mini-css-extract-plugin": ["mini-css-extract-plugin@2.9.2", "", { "dependencies": { "schema-utils": "^4.0.0", "tapable": "^2.2.1" }, "peerDependencies": { "webpack": "^5.0.0" } }, "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w=="], @@ -1514,6 +1503,10 @@ "piscina": ["piscina@5.0.0", "", { "optionalDependencies": { "@napi-rs/nice": "^1.0.1" } }, "sha512-R+arufwL7sZvGjAhSMK3TfH55YdGOqhpKXkcwQJr432AAnJX/xxX19PA4QisrmJ+BTTfZVggaz6HexbkQq1l1Q=="], + "playwright": ["playwright@1.52.0", "", { "dependencies": { "playwright-core": "1.52.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw=="], + + "playwright-core": ["playwright-core@1.52.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg=="], + "postcss": ["postcss@8.5.4", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w=="], "postcss-loader": ["postcss-loader@8.1.1", "", { "dependencies": { "cosmiconfig": "^9.0.0", "jiti": "^1.20.0", "semver": "^7.5.4" }, "peerDependencies": { "@rspack/core": "0.x || 1.x", "postcss": "^7.0.0 || ^8.0.1", "webpack": "^5.0.0" }, "optionalPeers": ["@rspack/core", "webpack"] }, "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ=="], @@ -1762,7 +1755,7 @@ "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - "typescript-eslint": ["typescript-eslint@8.33.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.33.1", "@typescript-eslint/parser": "8.33.1", "@typescript-eslint/utils": "8.33.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-AgRnV4sKkWOiZ0Kjbnf5ytTJXMUZQ0qhSVdQtDNYLPLnjsATEYhaO94GlRQwi4t4gO8FfjM6NnikHeKjUm8D7A=="], + "typescript-eslint": ["typescript-eslint@8.34.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.34.0", "@typescript-eslint/parser": "8.34.0", "@typescript-eslint/utils": "8.34.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ=="], "ua-parser-js": ["ua-parser-js@0.7.40", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-us1E3K+3jJppDBa3Tl0L3MOJiGhe1C6P0+nIvQAFYbxlMAx0h81eOwLmU57xgqToduDDPx3y5QsdjPfDu+FgOQ=="], @@ -1808,8 +1801,6 @@ "wbuf": ["wbuf@1.7.3", "", { "dependencies": { "minimalistic-assert": "^1.0.0" } }, "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA=="], - "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], - "weak-lru-cache": ["weak-lru-cache@1.2.2", "", {}, "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw=="], "webpack": ["webpack@5.99.8", "", { "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.14.0", "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^4.3.2", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.3.11", "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { "webpack": "bin/webpack.js" } }, "sha512-lQ3CPiSTpfOnrEGeXDwoq5hIGzSjmwD72GdfVzF7CQAI7t47rJG9eDWvcEkEn3CUQymAElVvDg3YNTlCYj+qUQ=="], @@ -1858,14 +1849,6 @@ "@angular-devkit/core/source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], - "@angular-eslint/builder/@angular-devkit/architect": ["@angular-devkit/architect@0.1902.14", "", { "dependencies": { "@angular-devkit/core": "19.2.14", "rxjs": "7.8.1" } }, "sha512-rgMkqOrxedzqLZ8w59T/0YrpWt7LDmGwt+ZhNHE7cn27jZ876yGC2Bhcn58YZh2+R03WEJ9q0ePblaBYz03SMw=="], - - "@angular-eslint/builder/@angular-devkit/core": ["@angular-devkit/core@19.2.14", "", { "dependencies": { "ajv": "8.17.1", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", "picomatch": "4.0.2", "rxjs": "7.8.1", "source-map": "0.7.4" }, "peerDependencies": { "chokidar": "^4.0.0" }, "optionalPeers": ["chokidar"] }, "sha512-aaPEnRNIBoYT4XrrYcZlHadX8vFDTUR+4wUgcmr0cNDLeWzWtoPFeVq8TQD6kFDeqovSx/UVEblGgg/28WvHyg=="], - - "@angular-eslint/schematics/@angular-devkit/core": ["@angular-devkit/core@19.2.14", "", { "dependencies": { "ajv": "8.17.1", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", "picomatch": "4.0.2", "rxjs": "7.8.1", "source-map": "0.7.4" }, "peerDependencies": { "chokidar": "^4.0.0" }, "optionalPeers": ["chokidar"] }, "sha512-aaPEnRNIBoYT4XrrYcZlHadX8vFDTUR+4wUgcmr0cNDLeWzWtoPFeVq8TQD6kFDeqovSx/UVEblGgg/28WvHyg=="], - - "@angular-eslint/schematics/@angular-devkit/schematics": ["@angular-devkit/schematics@19.2.14", "", { "dependencies": { "@angular-devkit/core": "19.2.14", "jsonc-parser": "3.3.1", "magic-string": "0.30.17", "ora": "5.4.1", "rxjs": "7.8.1" } }, "sha512-s89/MWXHy8+GP/cRfFbSECIG3FQQQwNVv44OOmghPVgKQgQ+EoE/zygL2hqKYTUPoPaS/IhNXdXjSE5pS9yLeg=="], - "@angular-eslint/schematics/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], "@babel/core/@babel/generator": ["@babel/generator@7.27.5", "", { "dependencies": { "@babel/parser": "^7.27.5", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw=="], @@ -1964,18 +1947,34 @@ "@tufjs/models/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.34.0", "", { "dependencies": { "@typescript-eslint/types": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.0" } }, "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw=="], + + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.34.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.34.0", "@typescript-eslint/types": "8.34.0", "@typescript-eslint/typescript-estree": "8.34.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + "@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.34.0", "", { "dependencies": { "@typescript-eslint/types": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.0" } }, "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw=="], + + "@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="], + + "@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.34.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.34.0", "@typescript-eslint/tsconfig-utils": "8.34.0", "@typescript-eslint/types": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg=="], + + "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.33.1", "", { "dependencies": { "@typescript-eslint/types": "8.33.1", "eslint-visitor-keys": "^4.2.0" } }, "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ=="], + + "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.34.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.34.0", "@typescript-eslint/tsconfig-utils": "8.34.0", "@typescript-eslint/types": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg=="], + + "@typescript-eslint/type-utils/@typescript-eslint/utils": ["@typescript-eslint/utils@8.34.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.34.0", "@typescript-eslint/types": "8.34.0", "@typescript-eslint/typescript-estree": "8.34.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ=="], + + "@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.33.1", "", { "dependencies": { "@typescript-eslint/types": "8.33.1", "eslint-visitor-keys": "^4.2.0" } }, "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ=="], + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="], + "accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], "adjust-sourcemap-loader/loader-utils": ["loader-utils@2.0.4", "", { "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", "json5": "^2.1.2" } }, "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw=="], - "angular-eslint/@angular-devkit/core": ["@angular-devkit/core@19.2.14", "", { "dependencies": { "ajv": "8.17.1", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", "picomatch": "4.0.2", "rxjs": "7.8.1", "source-map": "0.7.4" }, "peerDependencies": { "chokidar": "^4.0.0" }, "optionalPeers": ["chokidar"] }, "sha512-aaPEnRNIBoYT4XrrYcZlHadX8vFDTUR+4wUgcmr0cNDLeWzWtoPFeVq8TQD6kFDeqovSx/UVEblGgg/28WvHyg=="], - - "angular-eslint/@angular-devkit/schematics": ["@angular-devkit/schematics@19.2.14", "", { "dependencies": { "@angular-devkit/core": "19.2.14", "jsonc-parser": "3.3.1", "magic-string": "0.30.17", "ora": "5.4.1", "rxjs": "7.8.1" } }, "sha512-s89/MWXHy8+GP/cRfFbSECIG3FQQQwNVv44OOmghPVgKQgQ+EoE/zygL2hqKYTUPoPaS/IhNXdXjSE5pS9yLeg=="], - "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -2102,6 +2101,8 @@ "resolve-url-loader/loader-utils": ["loader-utils@2.0.4", "", { "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", "json5": "^2.1.2" } }, "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw=="], + "rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "schema-utils/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], @@ -2140,6 +2141,10 @@ "tar/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + "typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.34.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.34.0", "@typescript-eslint/types": "8.34.0", "@typescript-eslint/typescript-estree": "8.34.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ=="], + + "vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "vite/tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], "webpack/eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], @@ -2158,20 +2163,6 @@ "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "@angular-eslint/builder/@angular-devkit/architect/rxjs": ["rxjs@7.8.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg=="], - - "@angular-eslint/builder/@angular-devkit/core/rxjs": ["rxjs@7.8.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg=="], - - "@angular-eslint/builder/@angular-devkit/core/source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], - - "@angular-eslint/schematics/@angular-devkit/core/rxjs": ["rxjs@7.8.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg=="], - - "@angular-eslint/schematics/@angular-devkit/core/source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], - - "@angular-eslint/schematics/@angular-devkit/schematics/ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="], - - "@angular-eslint/schematics/@angular-devkit/schematics/rxjs": ["rxjs@7.8.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg=="], - "@eslint/eslintrc/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "@inquirer/core/wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -2216,16 +2207,32 @@ "@tufjs/models/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="], + + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="], + + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.34.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.34.0", "@typescript-eslint/tsconfig-utils": "8.34.0", "@typescript-eslint/types": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg=="], + + "@typescript-eslint/parser/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.34.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.34.0", "@typescript-eslint/types": "^8.34.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw=="], + + "@typescript-eslint/parser/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.34.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA=="], + + "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.34.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.34.0", "@typescript-eslint/types": "^8.34.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw=="], + + "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.34.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA=="], + + "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="], + + "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.34.0", "", { "dependencies": { "@typescript-eslint/types": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.0" } }, "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw=="], + + "@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], - "angular-eslint/@angular-devkit/core/rxjs": ["rxjs@7.8.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg=="], - - "angular-eslint/@angular-devkit/core/source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], - - "angular-eslint/@angular-devkit/schematics/ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="], - - "angular-eslint/@angular-devkit/schematics/rxjs": ["rxjs@7.8.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg=="], - "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "cacache/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -2266,6 +2273,8 @@ "karma-coverage/istanbul-lib-instrument/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "karma/chokidar/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "karma/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "karma/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], @@ -2306,6 +2315,14 @@ "tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.34.0", "", { "dependencies": { "@typescript-eslint/types": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.0" } }, "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw=="], + + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="], + + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.34.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.34.0", "@typescript-eslint/tsconfig-utils": "8.34.0", "@typescript-eslint/types": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg=="], + + "webpack-dev-server/chokidar/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "webpack-dev-server/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "webpack-dev-server/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], @@ -2320,16 +2337,6 @@ "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@angular-eslint/schematics/@angular-devkit/schematics/ora/cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="], - - "@angular-eslint/schematics/@angular-devkit/schematics/ora/is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="], - - "@angular-eslint/schematics/@angular-devkit/schematics/ora/is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="], - - "@angular-eslint/schematics/@angular-devkit/schematics/ora/log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="], - - "@angular-eslint/schematics/@angular-devkit/schematics/ora/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@inquirer/core/wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "@inquirer/core/wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -2346,15 +2353,15 @@ "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "angular-eslint/@angular-devkit/schematics/ora/cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.34.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.34.0", "@typescript-eslint/types": "^8.34.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw=="], - "angular-eslint/@angular-devkit/schematics/ora/is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.34.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA=="], - "angular-eslint/@angular-devkit/schematics/ora/is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - "angular-eslint/@angular-devkit/schematics/ora/log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="], + "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], - "angular-eslint/@angular-devkit/schematics/ora/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], "cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], @@ -2372,30 +2379,24 @@ "karma/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.34.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.34.0", "@typescript-eslint/types": "^8.34.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw=="], + + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.34.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA=="], + + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "webpack-dev-server/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@angular-eslint/schematics/@angular-devkit/schematics/ora/cli-cursor/restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="], - - "@angular-eslint/schematics/@angular-devkit/schematics/ora/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "angular-eslint/@angular-devkit/schematics/ora/cli-cursor/restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="], - - "angular-eslint/@angular-devkit/schematics/ora/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], "karma/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "karma/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@angular-eslint/schematics/@angular-devkit/schematics/ora/cli-cursor/restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], - - "@angular-eslint/schematics/@angular-devkit/schematics/ora/cli-cursor/restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - - "angular-eslint/@angular-devkit/schematics/ora/cli-cursor/restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], - - "angular-eslint/@angular-devkit/schematics/ora/cli-cursor/restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], } } diff --git a/frontend/e2e/backend.spec.ts b/frontend/e2e/backend.spec.ts new file mode 100644 index 0000000..4279610 --- /dev/null +++ b/frontend/e2e/backend.spec.ts @@ -0,0 +1,8 @@ +import { test, expect } from '@playwright/test'; + +test('backend works', async ({ page }) => { + await page.goto('http://localhost:8080/health'); + const response = await page.textContent('body'); + expect(response).toBeTruthy(); + expect(page.getByText('{"status":"UP"}')).toBeVisible(); +}); diff --git a/frontend/e2e/homepage.spec.ts b/frontend/e2e/homepage.spec.ts new file mode 100644 index 0000000..5455134 --- /dev/null +++ b/frontend/e2e/homepage.spec.ts @@ -0,0 +1,36 @@ +import { test, expect } from '@playwright/test'; + +test('home page loads correctly', async ({ page }) => { + await page.goto('/'); + + await expect(page).toHaveTitle(/Casino/); + await expect(page.getByRole('heading', { name: 'Willkommensbonus' })).toBeVisible(); + await expect(page.getByText('von bis zu €')).toBeVisible(); +}); + +test('registration popup should open and close', async ({ page }) => { + await page.goto('/'); + await page.getByRole('navigation').getByRole('button', { name: 'Jetzt registrieren' }).click(); + + await expect(page.getByText('Konto erstellenE-')).toBeVisible(); + + await page.getByRole('button', { name: 'Dialog schließen' }).click(); + + await expect(page.getByText('Konto erstellenE-')).not.toBeVisible(); +}); + +test('registration should work', async ({ page }) => { + await page.goto('/'); + await page.getByRole('navigation').getByRole('button', { name: 'Jetzt registrieren' }).click(); + + await page.getByRole('textbox', { name: 'E-Mail' }).fill('test@kjan.email'); + await page.getByRole('textbox', { name: 'Benutzername' }).fill('test-playwright'); + await page.getByRole('textbox', { name: 'Passwort' }).fill('BananaMan123'); + await page.locator('form').getByRole('button', { name: 'Registrieren' }).click(); + await page.getByRole('button', { name: 'Dialog schließen' }).click(); + await page.getByRole('navigation').getByRole('button', { name: 'Anmelden' }).click(); + await page.getByRole('textbox', { name: 'Benutzername oder E-Mail' }).fill('test@kjan.email'); + await page.getByRole('textbox', { name: 'Passwort' }).fill('BananaMan123'); + await page.locator('form').getByRole('button', { name: 'Anmelden' }).click(); + await expect(page.getByText('Email not verified')).toBeVisible(); +}); diff --git a/frontend/package.json b/frontend/package.json index 5a9aa3c..c86fa50 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -39,13 +39,14 @@ "tslib": "^2.3.0" }, "devDependencies": { - "@angular/compiler-cli": "^20.0.0", "@angular-devkit/build-angular": "^20.0.0", "@angular/cli": "^20.0.0", + "@angular/compiler-cli": "^20.0.0", + "@playwright/test": "^1.52.0", "@types/jasmine": "~5.1.0", - "angular-eslint": "19.7.1", + "angular-eslint": "20.0.0", "eslint": "^9.28.0", - "jasmine-core": "~5.7.0", + "jasmine-core": "~5.8.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", "karma-coverage": "~2.2.0", @@ -53,6 +54,6 @@ "karma-jasmine-html-reporter": "~2.1.0", "prettier": "^3.4.2", "typescript": "~5.8.0", - "typescript-eslint": "8.33.1" + "typescript-eslint": "8.34.0" } } diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts new file mode 100644 index 0000000..8d1e8c7 --- /dev/null +++ b/frontend/playwright.config.ts @@ -0,0 +1,49 @@ +// playwright.config.ts (or .js) +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './e2e', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + // This baseURL is for your frontend tests. + // Tests hitting the backend directly will use absolute URLs. + baseURL: 'http://localhost:4200', + trace: 'on-first-retry', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + ], + + webServer: { + command: + 'cd .. && conc -n "frontend,backend" "cd frontend && bun run start" "cd backend/ && watchexec -r -e java ./gradlew :bootRun"', + // **IMPORTANT CHANGE HERE:** + // Point to your backend's health check endpoint. + // If your Spring Boot app uses Actuator, it might be /actuator/health + // Verify the correct health endpoint for your backend. + url: 'http://localhost:8080/health', // Or "http://localhost:8080/actuator/health" + reuseExistingServer: !process.env.CI, + // **INCREASE TIMEOUT SIGNIFICANTLY** + // Gradle + Spring Boot can take a while, especially on first run or in CI. + // Adjust as needed, e.g., 3-5 minutes. + timeout: 300 * 1000, // 300 seconds = 5 minutes + stdout: 'pipe', // Good for capturing logs in CI reports + stderr: 'pipe', + // Optional: If your server needs specific environment variables + // env: { + // SPRING_PROFILES_ACTIVE: 'test', // Example for Spring Boot + // }, + }, +}); diff --git a/frontend/src/app/feature/auth/login/login.component.ts b/frontend/src/app/feature/auth/login/login.component.ts index 09c1cdf..ff1305a 100644 --- a/frontend/src/app/feature/auth/login/login.component.ts +++ b/frontend/src/app/feature/auth/login/login.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Output, signal } from '@angular/core'; +import { Component, EventEmitter, Output, signal, inject } from '@angular/core'; import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { LoginRequest } from '../../../model/auth/LoginRequest'; @@ -20,11 +20,11 @@ export class LoginComponent { @Output() closeDialog = new EventEmitter(); @Output() forgotPassword = new EventEmitter(); - constructor( - private fb: FormBuilder, - private authService: AuthService, - private router: Router - ) { + private fb = inject(FormBuilder); + private authService = inject(AuthService); + private router = inject(Router); + + constructor() { this.loginForm = this.fb.group({ usernameOrEmail: ['', [Validators.required]], password: ['', [Validators.required]], diff --git a/frontend/src/app/feature/auth/recover-password/recover-password.component.ts b/frontend/src/app/feature/auth/recover-password/recover-password.component.ts index 89f5d96..61c5af5 100644 --- a/frontend/src/app/feature/auth/recover-password/recover-password.component.ts +++ b/frontend/src/app/feature/auth/recover-password/recover-password.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Output, signal, OnInit } from '@angular/core'; +import { Component, EventEmitter, Output, signal, OnInit, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; @@ -22,12 +22,12 @@ export default class RecoverPasswordComponent implements OnInit { @Output() closeDialog = new EventEmitter(); @Output() switchToLogin = new EventEmitter(); - constructor( - private fb: FormBuilder, - private authService: AuthService, - private router: Router, - private route: ActivatedRoute - ) { + private fb = inject(FormBuilder); + private authService = inject(AuthService); + private router = inject(Router); + private route = inject(ActivatedRoute); + + constructor() { this.emailForm = this.fb.group({ email: ['', [Validators.required, Validators.email]], }); diff --git a/frontend/src/app/feature/auth/register/register.component.ts b/frontend/src/app/feature/auth/register/register.component.ts index 8ad38bb..c1ddf83 100644 --- a/frontend/src/app/feature/auth/register/register.component.ts +++ b/frontend/src/app/feature/auth/register/register.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Output, signal } from '@angular/core'; +import { Component, EventEmitter, Output, signal, inject } from '@angular/core'; import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { RegisterRequest } from '../../../model/auth/RegisterRequest'; import { AuthService } from '@service/auth.service'; @@ -19,10 +19,10 @@ export class RegisterComponent { @Output() switchForm = new EventEmitter(); @Output() closeDialog = new EventEmitter(); - constructor( - private fb: FormBuilder, - private authService: AuthService - ) { + private fb = inject(FormBuilder); + private authService = inject(AuthService); + + constructor() { this.registerForm = this.fb.group({ email: ['', [Validators.required, Validators.email]], username: ['', [Validators.required, Validators.minLength(3)]], diff --git a/frontend/src/app/feature/game/blackjack/components/dealer-hand/dealer-hand.component.ts b/frontend/src/app/feature/game/blackjack/components/dealer-hand/dealer-hand.component.ts index 3674d63..5ea513f 100644 --- a/frontend/src/app/feature/game/blackjack/components/dealer-hand/dealer-hand.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/dealer-hand/dealer-hand.component.ts @@ -1,4 +1,11 @@ -import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + Input, + OnChanges, + SimpleChanges, + inject, +} from '@angular/core'; import { CommonModule } from '@angular/common'; import { Card } from '@blackjack/models/blackjack.model'; import { PlayingCardComponent } from '../playing-card/playing-card.component'; @@ -47,7 +54,7 @@ export class DealerHandComponent implements OnChanges { private lastCardCount = 0; - constructor(protected gameControlsService: GameControlsService) {} + protected gameControlsService = inject(GameControlsService); ngOnChanges(changes: SimpleChanges): void { if (changes['cards']) { diff --git a/frontend/src/app/feature/game/blackjack/components/game-controls/game-controls.component.ts b/frontend/src/app/feature/game/blackjack/components/game-controls/game-controls.component.ts index 9799b08..a782183 100644 --- a/frontend/src/app/feature/game/blackjack/components/game-controls/game-controls.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/game-controls/game-controls.component.ts @@ -1,4 +1,11 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + Output, + inject, +} from '@angular/core'; import { CommonModule } from '@angular/common'; import { GameState } from '@blackjack/enum/gameState'; import { Card } from '@blackjack/models/blackjack.model'; @@ -69,7 +76,7 @@ export class GameControlsComponent { protected readonly GameState = GameState; - constructor(protected gameControlsService: GameControlsService) {} + protected gameControlsService = inject(GameControlsService); get canDoubleDown(): boolean { return ( diff --git a/frontend/src/app/feature/game/blackjack/components/game-info/game-info.component.ts b/frontend/src/app/feature/game/blackjack/components/game-info/game-info.component.ts index 644fb22..6f3c7b2 100644 --- a/frontend/src/app/feature/game/blackjack/components/game-info/game-info.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/game-info/game-info.component.ts @@ -7,6 +7,7 @@ import { Output, signal, SimpleChanges, + inject, } from '@angular/core'; import { CommonModule, CurrencyPipe } from '@angular/common'; import { FormGroup, ReactiveFormsModule } from '@angular/forms'; @@ -121,7 +122,9 @@ export class GameInfoComponent implements OnChanges { betForm: FormGroup; - constructor(private bettingService: BettingService) { + private bettingService = inject(BettingService); + + constructor() { this.betForm = this.bettingService.createBetForm(); } diff --git a/frontend/src/app/feature/game/blackjack/components/player-hand/player-hand.component.ts b/frontend/src/app/feature/game/blackjack/components/player-hand/player-hand.component.ts index bca976c..41da0f8 100644 --- a/frontend/src/app/feature/game/blackjack/components/player-hand/player-hand.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/player-hand/player-hand.component.ts @@ -1,4 +1,11 @@ -import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + Input, + OnChanges, + SimpleChanges, + inject, +} from '@angular/core'; import { CommonModule } from '@angular/common'; import { PlayingCardComponent } from '../playing-card/playing-card.component'; import { Card } from '@blackjack/models/blackjack.model'; @@ -49,7 +56,7 @@ export class PlayerHandComponent implements OnChanges { private lastCardCount = 0; - constructor(protected gameControlsService: GameControlsService) {} + protected gameControlsService = inject(GameControlsService); ngOnChanges(changes: SimpleChanges): void { if (changes['cards']) { diff --git a/frontend/src/app/feature/game/blackjack/components/playing-card/playing-card.component.ts b/frontend/src/app/feature/game/blackjack/components/playing-card/playing-card.component.ts index 0fbbb5a..f3f1cda 100644 --- a/frontend/src/app/feature/game/blackjack/components/playing-card/playing-card.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/playing-card/playing-card.component.ts @@ -6,6 +6,7 @@ import { Input, OnChanges, SimpleChanges, + inject, } from '@angular/core'; import { CommonModule } from '@angular/common'; import { gsap } from 'gsap'; @@ -58,7 +59,7 @@ export class PlayingCardComponent implements AfterViewInit, OnChanges { @Input({ required: true }) hidden!: boolean; @Input() isNew = false; - constructor(private elementRef: ElementRef) {} + private elementRef = inject(ElementRef); get isRedSuit(): boolean { return this.suit === 'HEARTS' || this.suit === 'DIAMONDS'; diff --git a/frontend/src/app/feature/game/blackjack/services/betting.service.ts b/frontend/src/app/feature/game/blackjack/services/betting.service.ts index 347f19b..186218b 100644 --- a/frontend/src/app/feature/game/blackjack/services/betting.service.ts +++ b/frontend/src/app/feature/game/blackjack/services/betting.service.ts @@ -1,11 +1,11 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; @Injectable({ providedIn: 'root', }) export class BettingService { - constructor(private fb: FormBuilder) {} + private fb = inject(FormBuilder); createBetForm(): FormGroup { return this.fb.group({ diff --git a/frontend/src/app/feature/game/dice/dice.service.ts b/frontend/src/app/feature/game/dice/dice.service.ts index a071495..e3a7bed 100644 --- a/frontend/src/app/feature/game/dice/dice.service.ts +++ b/frontend/src/app/feature/game/dice/dice.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { DiceDto, DiceResult } from './dice.model'; @@ -10,7 +10,7 @@ import { environment } from '@environments/environment'; export class DiceService { private apiUrl = `${environment.apiUrl}/dice`; - constructor(private http: HttpClient) {} + private http = inject(HttpClient); rollDice(diceDto: DiceDto): Observable { return this.http.post(this.apiUrl, diceDto); diff --git a/frontend/src/app/feature/home/home.component.ts b/frontend/src/app/feature/home/home.component.ts index f032246..18e6de2 100644 --- a/frontend/src/app/feature/home/home.component.ts +++ b/frontend/src/app/feature/home/home.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnInit, inject } from '@angular/core'; import { NgFor } from '@angular/common'; import { ActivatedRoute, Router } from '@angular/router'; import { Game } from 'app/model/Game'; @@ -14,10 +14,8 @@ import format from 'ajv/dist/vocabularies/format'; export default class HomeComponent implements OnInit { isDepositSuccessful = false; - constructor( - public route: ActivatedRoute, - public router: Router - ) {} + public route = inject(ActivatedRoute); + public router = inject(Router); ngOnInit() { this.isDepositSuccessful = this.route.snapshot.queryParams['success'] == 'true'; diff --git a/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.ts b/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.ts index 3faf5be..64ea632 100644 --- a/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.ts +++ b/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component } from '@angular/core'; +import { ChangeDetectorRef, Component, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ActivatedRoute, Router } from '@angular/router'; import { LootboxService } from '../services/lootbox.service'; @@ -26,14 +26,14 @@ export default class LootboxOpeningComponent { currentUser: User | null = null; private winSound: HTMLAudioElement; - constructor( - private route: ActivatedRoute, - private router: Router, - private lootboxService: LootboxService, - private userService: UserService, - private authService: AuthService, - private cdr: ChangeDetectorRef - ) { + private route = inject(ActivatedRoute); + private router = inject(Router); + private lootboxService = inject(LootboxService); + private userService = inject(UserService); + private authService = inject(AuthService); + private cdr = inject(ChangeDetectorRef); + + constructor() { this.winSound = new Audio('/sounds/win.mp3'); this.loadLootbox(); this.authService.userSubject.subscribe((user) => { diff --git a/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.ts b/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.ts index 373199b..7fc3201 100644 --- a/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.ts +++ b/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, OnInit, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { LootboxService } from '../services/lootbox.service'; import { LootBox } from 'app/model/LootBox'; @@ -86,13 +86,11 @@ export default class LootboxSelectionComponent implements OnInit { }, ]; - constructor( - private lootboxService: LootboxService, - private router: Router, - private cdr: ChangeDetectorRef, - private authService: AuthService, - private userService: UserService - ) {} + private lootboxService = inject(LootboxService); + private router = inject(Router); + private cdr = inject(ChangeDetectorRef); + private authService = inject(AuthService); + private userService = inject(UserService); ngOnInit(): void { this.loadLootboxes(); diff --git a/frontend/src/app/service/auth.service.ts b/frontend/src/app/service/auth.service.ts index 61b3e7d..40d9f6d 100644 --- a/frontend/src/app/service/auth.service.ts +++ b/frontend/src/app/service/auth.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { BehaviorSubject, Observable, tap } from 'rxjs'; import { Router, ActivatedRoute } from '@angular/router'; @@ -19,13 +19,13 @@ export class AuthService { private userUrl = `${environment.apiUrl}/users`; private oauthUrl = `${environment.apiUrl}/oauth2`; + private http = inject(HttpClient); + private router = inject(Router); + private route = inject(ActivatedRoute); + userSubject: BehaviorSubject; - constructor( - private http: HttpClient, - private router: Router, - private route: ActivatedRoute - ) { + constructor() { this.userSubject = new BehaviorSubject(this.getUserFromStorage()); // Check for token in URL (OAuth callback) on initialization diff --git a/frontend/src/app/shared/components/confirmation/confirmation.component.ts b/frontend/src/app/shared/components/confirmation/confirmation.component.ts index 9ce91ba..bc91d62 100644 --- a/frontend/src/app/shared/components/confirmation/confirmation.component.ts +++ b/frontend/src/app/shared/components/confirmation/confirmation.component.ts @@ -7,6 +7,7 @@ import { OnDestroy, Output, ViewChild, + inject, } from '@angular/core'; import { ModalAnimationService } from '@shared/services/modal-animation.service'; import gsap from 'gsap'; @@ -23,7 +24,7 @@ export class ConfirmationComponent implements AfterViewInit, OnDestroy { @ViewChild('modalBg') modalBg!: ElementRef; @ViewChild('modalCard') modalCard!: ElementRef; - constructor(private modalAnimationService: ModalAnimationService) {} + private modalAnimationService = inject(ModalAnimationService); ngAfterViewInit() { if (this.successful) { diff --git a/frontend/src/app/shared/services/sound-initializer.service.ts b/frontend/src/app/shared/services/sound-initializer.service.ts index 47d09e0..90a0e15 100644 --- a/frontend/src/app/shared/services/sound-initializer.service.ts +++ b/frontend/src/app/shared/services/sound-initializer.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Renderer2, RendererFactory2 } from '@angular/core'; +import { Injectable, Renderer2, RendererFactory2, inject } from '@angular/core'; @Injectable({ providedIn: 'root', @@ -7,8 +7,10 @@ export class SoundInitializerService { private renderer: Renderer2; private observer: MutationObserver; - constructor(rendererFactory: RendererFactory2) { - this.renderer = rendererFactory.createRenderer(null, null); + private rendererFactory = inject(RendererFactory2); + + constructor() { + this.renderer = this.rendererFactory.createRenderer(null, null); this.observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { diff --git a/justfile b/justfile index 4fc777c..c574ea0 100644 --- a/justfile +++ b/justfile @@ -6,8 +6,7 @@ info: start: command -v concurrently &> /dev/null || bun add -g concurrently command -v watchexec &> /dev/null || brew install watchexec - docker compose up -d - conc -n "frontend,backend" "cd frontend && bun run start" "cd backend/ && watchexec -r -e java ./gradlew :bootRun" + conc -n "frontend,backend,docker" "cd frontend && bun run start" "cd backend/ && watchexec -r -e java ./gradlew :bootRun" "docker compose up" # Builds both the backend and frontend docker images (obv) build: diff --git a/projektdokumentation b/projektdokumentation deleted file mode 160000 index 1a3bf32..0000000 --- a/projektdokumentation +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1a3bf323a97f6eb445287616a080ad542298305b diff --git a/projektdokumentation/.gitignore b/projektdokumentation/.gitignore new file mode 100644 index 0000000..5193c8d --- /dev/null +++ b/projektdokumentation/.gitignore @@ -0,0 +1,132 @@ +## Core latex/pdflatex auxiliary files: +*.aux +*.lof +*.log +*.lot +*.fls +*.out +*.toc +*.pdf +## Intermediate documents: +*.dvi +*-converted-to.* +# these rules might exclude image files for figures etc. +# *.ps +# *.eps +# *.pdf + +## Bibliography auxiliary files (bibtex/biblatex/biber): +*.bbl +*.bcf +*.blg +*-blx.aux +*-blx.bib +*.brf +*.run.xml + +## Build tool auxiliary files: +*.fdb_latexmk +*.synctex +*.synctex.gz +*.synctex.gz(busy) +*.pdfsync + +## Auxiliary and intermediate files from other packages: + +# algorithms +*.alg +*.loa + +# achemso +acs-*.bib + +# amsthm +*.thm + +# beamer +*.nav +*.snm +*.vrb + +#(e)ledmac/(e)ledpar +*.end +*.[1-9] +*.[1-9][0-9] +*.[1-9][0-9][0-9] +*.[1-9]R +*.[1-9][0-9]R +*.[1-9][0-9][0-9]R +*.eledsec[1-9] +*.eledsec[1-9]R +*.eledsec[1-9][0-9] +*.eledsec[1-9][0-9]R +*.eledsec[1-9][0-9][0-9] +*.eledsec[1-9][0-9][0-9]R + +# glossaries +*.acn +*.acr +*.glg +*.glo +*.gls + +# gnuplottex +*-gnuplottex-* + +# hyperref +*.brf + +# knitr +*-concordance.tex +*.tikz +*-tikzDictionary + +# listings +*.lol + +# makeidx +*.idx +*.ilg +*.ind +*.ist + +# minitoc +*.maf +*.mtc +*.mtc0 + +# minted +_minted* +*.pyg + +# morewrites +*.mw + +# nomencl +*.nlo + +# sagetex +*.sagetex.sage +*.sagetex.py +*.sagetex.scmd + +# sympy +*.sout +*.sympy +sympy-plots-for-*.tex/ + +# todonotes +*.tdo + +# xindy +*.xdy + +# WinEdt +*.bak +*.sav +/tmp/ + +# idea +.idea/ + +Projektdokumentation.syntex(busy) \ No newline at end of file diff --git a/projektdokumentation/.project b/projektdokumentation/.project new file mode 100644 index 0000000..f9bd2d8 --- /dev/null +++ b/projektdokumentation/.project @@ -0,0 +1,17 @@ + + + VorlageFachinformatiker + + + + + + net.sourceforge.texlipse.builder.TexlipseBuilder + + + + + + net.sourceforge.texlipse.builder.TexlipseNature + + diff --git a/projektdokumentation/.texlipse b/projektdokumentation/.texlipse new file mode 100644 index 0000000..f5943d9 --- /dev/null +++ b/projektdokumentation/.texlipse @@ -0,0 +1,13 @@ +#TeXlipse project settings +#Fri Jan 27 09:15:16 CET 2012 +builderNum=2 +outputDir= +makeIndSty= +bibrefDir= +outputFormat=pdf +tempDir=tmp +mainTexFile=Projektdokumentation.tex +outputFile=Projektdokumentation.pdf +langSpell=de +markDer=true +srcDir= diff --git a/projektdokumentation/Abkuerzungen.tex b/projektdokumentation/Abkuerzungen.tex new file mode 100644 index 0000000..f0fda5c --- /dev/null +++ b/projektdokumentation/Abkuerzungen.tex @@ -0,0 +1,23 @@ +% !TEX root = Projektdokumentation.tex + +% Es werden nur die Abkürzungen aufgelistet, die mit \ac definiert und auch benutzt wurden. +% +% \acro{VERSIS}{Versicherungsinformationssystem\acroextra{ (Bestandsführungssystem)}} +% Ergibt in der Liste: VERSIS Versicherungsinformationssystem (Bestandsführungssystem) +% Im Text aber: \ac{VERSIS} -> Versicherungsinformationssystem (VERSIS) + +% Hinweis: allgemein bekannte Abkürzungen wie z.B. bzw. u.a. müssen nicht ins Abkürzungsverzeichnis aufgenommen werden +% Hinweis: allgemein bekannte IT-Begriffe wie Datenbank oder Programmiersprache müssen nicht erläutert werden, +% aber ggfs. Fachbegriffe aus der Domäne des Prüflings (z.B. Versicherung) + +% Die Option (in den eckigen Klammern) enthält das längste Label oder +% einen Platzhalter der die Breite der linken Spalte bestimmt. +\begin{acronym}[WWWWW] + \acro{CI}{Continuous Integration} + \acro{CI/CD}{Continuous Integration/Continuous Deployment} + \acro{E2E}{End-to-End} + \acro{API}{Application Programming Interface} + \acro{JSON}{JavaScript Object Notation} + \acro{HTTP}{Hypertext Transfer Protocol} + \acro{JWT}{JSON Web Token} +\end{acronym} diff --git a/projektdokumentation/Allgemein/Befehle.tex b/projektdokumentation/Allgemein/Befehle.tex new file mode 100644 index 0000000..3bb2025 --- /dev/null +++ b/projektdokumentation/Allgemein/Befehle.tex @@ -0,0 +1,85 @@ +% !TEX root = ../Projektdokumentation.tex + +% Abkürzungen, ggfs. mit korrektem Leerraum +\newcommand{\bs}{$\backslash$\xspace} +\newcommand{\bspw}{bspw.\xspace} +\newcommand{\bzw}{bzw.\xspace} +\newcommand{\ca}{ca.\xspace} +\newcommand{\dahe}{\mbox{d.\,h.}\xspace} +\newcommand{\etc}{etc.\xspace} +\newcommand{\eur}[1]{\mbox{#1\,\texteuro}\xspace} +\newcommand{\evtl}{evtl.\xspace} +\newcommand{\ggfs}{ggfs.\xspace} +\newcommand{\Ggfs}{Ggfs.\xspace} +\newcommand{\gqq}[1]{\glqq{}#1\grqq{}} +\newcommand{\inkl}{inkl.\xspace} +\newcommand{\insb}{insb.\xspace} +\newcommand{\ua}{\mbox{u.\,a.}\xspace} +\newcommand{\usw}{usw.\xspace} +\newcommand{\Vgl}{Vgl.\xspace} +\newcommand{\zB}{\mbox{z.\,B.}\xspace} + +% Befehle für häufig anfallende Aufgaben +\newcommand{\Abbildung}[1]{\autoref{fig:#1}} +\newcommand{\Anhang}[1]{\appendixname{}~\ref{#1}: \nameref{#1} \vpageref{#1}} +\newcommand{\includegraphicsKeepAspectRatio}[2]{\includegraphics[width=#2\textwidth,height=#2\textheight,keepaspectratio]{#1}} +\newcommand{\Zitat}[2][\empty]{\ifthenelse{\equal{#1}{\empty}}{\citep{#2}}{\citep[#1]{#2}}} +\newcommand{\Autor}[1]{\textsc{#1}} % zum Ausgeben von Autoren +\newcommand{\itemd}[2]{\item{\textbf{#1}}\\{#2}} % erzeugt ein Listenelement mit fetter Überschrift + +% fügt Tabellen aus einer TEX-Datei ein +\newcommand{\tabelle}[3] % Parameter: caption, label, file +{\begin{table}[htbp] +\centering +\singlespacing +\input{Tabellen/#3} +\caption{#1} +\label{#2} +\end{table}} + +\newcommand{\tabelleAnhang}[1] % Parameter: file +{\begin{center} +\singlespacing +\input{Tabellen/#1} +\end{center}} + +% einfaches Wechseln der Schrift, z.B.: \changefont{cmss}{sbc}{n} +\newcommand{\changefont}[3]{\fontfamily{#1} \fontseries{#2} \fontshape{#3} \selectfont} + +% Verwendung analog zu \includegraphics +\newlength{\myx} % Variable zum Speichern der Bildbreite +\newlength{\myy} % Variable zum Speichern der Bildhöhe +\newcommand\includegraphicstotab[2][\relax]{% +% Abspeichern der Bildabmessungen +\settowidth{\myx}{\includegraphics[{#1}]{#2}}% +\settoheight{\myy}{\includegraphics[{#1}]{#2}}% +% das eigentliche Einfügen +\parbox[c][1.1\myy][c]{\myx}{% +\includegraphics[{#1}]{#2}}% +} + +\definecolor{AOBlau}{rgb}{0, 0.28, 0.56} + +% verschiedene Befehle um Wörter semantisch auszuzeichnen ---------------------- +\newcommand{\Index}[2][\empty]{\ifthenelse{\equal{#1}{\empty}}{\index{#2}#2}{\index{#1}#2}} +\newcommand{\Fachbegriff}[2][\empty]{\ifthenelse{\equal{#1}{\empty}}{\textit{\Index{#2}}}{\textit{\Index[#1]{#2}}}} +\newcommand{\NeuerBegriff}[2][\empty]{\ifthenelse{\equal{#1}{\empty}}{\textbf{\Index{#2}}}{\textbf{\Index[#1]{#2}}}} + +\newcommand{\Ausgabe}[1]{\texttt{#1}} +\newcommand{\Eingabe}[1]{\texttt{#1}} +\newcommand{\Code}[1]{\texttt{#1}} +\newcommand{\Datei}[1]{\texttt{#1}} + +\newcommand{\Assembly}[1]{\textsf{#1}} +\newcommand{\Klasse}[1]{\textsf{#1}} +\newcommand{\Methode}[1]{\textsf{#1}} +\newcommand{\Attribut}[1]{\textsf{#1}} + +\newcommand{\Datentyp}[1]{\textsf{#1}} +\newcommand{\XMLElement}[1]{\textsf{#1}} +\newcommand{\Webservice}[1]{\textsf{#1}} + +\newcommand{\Refactoring}[1]{\Fachbegriff{#1}} +\newcommand{\CodeSmell}[1]{\Fachbegriff{#1}} +\newcommand{\Metrik}[1]{\Fachbegriff{#1}} +\newcommand{\DesignPattern}[1]{\Fachbegriff{#1}} diff --git a/projektdokumentation/Allgemein/Packages.tex b/projektdokumentation/Allgemein/Packages.tex new file mode 100644 index 0000000..318cc14 --- /dev/null +++ b/projektdokumentation/Allgemein/Packages.tex @@ -0,0 +1,196 @@ +% !TEX root = ../Projektdokumentation.tex + +% Anpassung an Landessprache --------------------------------------------------- +\usepackage{babel} + +% Umlaute ---------------------------------------------------------------------- +% Umlaute/Sonderzeichen wie äüöß direkt im Quelltext verwenden (CodePage). +% Erlaubt automatische Trennung von Worten mit Umlauten. +% ------------------------------------------------------------------------------ +\usepackage[T1]{fontenc} +\usepackage{textcomp} % Euro-Zeichen etc. + +% Schrift ---------------------------------------------------------------------- +\usepackage{lmodern} % bessere Fonts +\usepackage{relsize} % Schriftgröße relativ festlegen + +% Tabellen --------------------------------------------------------------------- +\PassOptionsToPackage{table}{xcolor} +\usepackage{tabularx} +% für lange Tabellen +\usepackage{longtable} +\usepackage{array} +\usepackage{ragged2e} +\usepackage{lscape} +\newcolumntype{w}[1]{>{\raggedleft\hspace{0pt}}p{#1}} % Spaltendefinition rechtsbündig mit definierter Breite + +% Grafiken --------------------------------------------------------------------- +\usepackage[dvips,final]{graphicx} % Einbinden von JPG-Grafiken ermöglichen +\usepackage{graphics} % keepaspectratio +\usepackage{floatflt} % zum Umfließen von Bildern +\graphicspath{{Bilder/}} % hier liegen die Bilder des Dokuments + +% Sonstiges -------------------------------------------------------------------- +\usepackage[titles]{tocloft} % Inhaltsverzeichnis DIN 5008 gerecht einrücken + +% Explicitly set German titles for ToC, LoF, LoT +\renewcommand{\contentsname}{Inhaltsverzeichnis} +\renewcommand{\listfigurename}{Abbildungsverzeichnis} +\renewcommand{\listtablename}{Tabellenverzeichnis} + +\usepackage{amsmath,amsfonts} % Befehle aus AMSTeX für mathematische Symbole +\usepackage{enumitem} % anpassbare Enumerates/Itemizes +\usepackage{xspace} % sorgt dafür, dass Leerzeichen hinter parameterlosen Makros nicht als Makroendezeichen interpretiert werden + +\usepackage{makeidx} % für Index-Ausgabe mit \printindex +\usepackage[printonlyused]{acronym} % es werden nur benutzte Definitionen aufgelistet + +% Einfache Definition der Zeilenabstände und Seitenränder etc. +\usepackage{setspace} +\usepackage{geometry} + +% Symbolverzeichnis +\usepackage[intoc]{nomencl} +\let\abbrev\nomenclature +\renewcommand{\nomname}{Abkürzungsverzeichnis} +\setlength{\nomlabelwidth}{.25\hsize} +\renewcommand{\nomlabel}[1]{#1 \dotfill} +\setlength{\nomitemsep}{-\parsep} + +\usepackage{varioref} % Elegantere Verweise. „auf der nächsten Seite“ +\usepackage{url} % URL verlinken, lange URLs umbrechen etc. + +\usepackage{chngcntr} % fortlaufendes Durchnummerieren der Fußnoten +% \usepackage[perpage]{footmisc} % Alternative: Nummerierung der Fußnoten auf jeder Seite neu + +\usepackage{ifthen} % bei der Definition eigener Befehle benötigt +\usepackage{todonotes} % definiert u.a. die Befehle \todo und \listoftodos +\usepackage[square]{natbib} % wichtig für korrekte Zitierweise + +% PDF-Optionen ----------------------------------------------------------------- +\usepackage{pdfpages} +\pdfminorversion=5 % erlaubt das Einfügen von pdf-Dateien bis Version 1.7, ohne eine Fehlermeldung zu werfen (keine Garantie für fehlerfreies Einbetten!) +\usepackage[ + bookmarks, + bookmarksnumbered, + bookmarksopen=true, + bookmarksopenlevel=1, + colorlinks=true, +% diese Farbdefinitionen zeichnen Links im PDF farblich aus + linkcolor=AOBlau, % einfache interne Verknüpfungen + anchorcolor=AOBlau,% Ankertext + citecolor=AOBlau, % Verweise auf Literaturverzeichniseinträge im Text + filecolor=AOBlau, % Verknüpfungen, die lokale Dateien öffnen + menucolor=AOBlau, % Acrobat-Menüpunkte + urlcolor=AOBlau, +% diese Farbdefinitionen sollten für den Druck verwendet werden (alles schwarz) + %linkcolor=black, % einfache interne Verknüpfungen + %anchorcolor=black, % Ankertext + %citecolor=black, % Verweise auf Literaturverzeichniseinträge im Text + %filecolor=black, % Verknüpfungen, die lokale Dateien öffnen + %menucolor=black, % Acrobat-Menüpunkte + %urlcolor=black, +% + %backref, % Quellen werden zurück auf ihre Zitate verlinkt + pdftex, + plainpages=false, % zur korrekten Erstellung der Bookmarks + pdfpagelabels=true, % zur korrekten Erstellung der Bookmarks + hypertexnames=false, % zur korrekten Erstellung der Bookmarks + linktocpage % Seitenzahlen anstatt Text im Inhaltsverzeichnis verlinken +]{hyperref} +% Befehle, die Umlaute ausgeben, führen zu Fehlern, wenn sie hyperref als Optionen übergeben werden +\hypersetup{ + pdftitle={\titel -- \untertitel}, + pdfauthor={\autorName}, + pdfcreator={\autorName}, + pdfsubject={\titel -- \untertitel}, + pdfkeywords={\titel -- \untertitel}, +} + + +% zum Einbinden von Programmcode ----------------------------------------------- +\usepackage{listings} +% Explicitly set German title for LoL +\renewcommand{\lstlistlistingname}{Codeverzeichnis} % For list of listings +\usepackage{xcolor} +\definecolor{hellgelb}{rgb}{1,1,0.9} +\definecolor{colKeys}{rgb}{0,0,1} +\definecolor{colIdentifier}{rgb}{0,0,0} +\definecolor{colComments}{rgb}{0,0.5,0} +\definecolor{colString}{rgb}{1,0,0} +\lstset{ + float=hbp, + basicstyle=\footnotesize, + identifierstyle=\color{colIdentifier}, + keywordstyle=\color{colKeys}, + stringstyle=\color{colString}, + commentstyle=\color{colComments}, + backgroundcolor=\color{hellgelb}, + columns=flexible, + tabsize=2, + frame=single, + extendedchars=true, + showspaces=false, + showstringspaces=false, + numbers=left, + numberstyle=\tiny, + breaklines=true, + breakautoindent=true, + captionpos=b, +} +\lstdefinelanguage{cs}{ + sensitive=false, + morecomment=[l]{//}, + morecomment=[s]{/*}{*/}, + morestring=[b]", + morekeywords={ + abstract,event,new,struct,as,explicit,null,switch + base,extern,object,this,bool,false,operator,throw, + break,finally,out,true,byte,fixed,override,try, + case,float,params,typeof,catch,for,private,uint, + char,foreach,protected,ulong,checked,goto,public,unchecked, + class,if,readonly,unsafe,const,implicit,ref,ushort, + continue,in,return,using,decimal,int,sbyte,virtual, + default,interface,sealed,volatile,delegate,internal,short,void, + do,is,sizeof,while,double,lock,stackalloc, + else,long,static,enum,namespace,string}, +} +\lstdefinelanguage{natural}{ + sensitive=false, + morecomment=[l]{/*}, + morestring=[b]", + morestring=[b]', + alsodigit={-,*}, + morekeywords={ + DEFINE,DATA,LOCAL,END-DEFINE,WRITE,CALLNAT,PARAMETER,USING, + IF,NOT,END-IF,ON,*ERROR-NR,ERROR,END-ERROR,ESCAPE,ROUTINE, + PERFORM,SUBROUTINE,END-SUBROUTINE,CONST,END-FOR,END,FOR,RESIZE, + ARRAY,TO,BY,VALUE,RESET,COMPRESS,INTO,EQ}, +} +\lstdefinelanguage{php}{ + sensitive=false, + morecomment=[l]{/*}, + morestring=[b]", + morestring=[b]', + alsodigit={-,*}, + morekeywords={ + abstract,and,array,as,break,case,catch,cfunction,class,clone,const, + continue,declare,default,do,else,elseif,enddeclare,endfor,endforeach, + endif,endswitch,endwhile,extends,final,for,foreach,function,global, + goto,if,implements,interface,instanceof,namespace,new,old_function,or, + private,protected,public,static,switch,throw,try,use,var,while,xor + die,echo,empty,exit,eval,include,include_once,isset,list,require, + require_once,return,print,unset}, +} +\lstdefinelanguage{json}{ + basicstyle=\ttfamily\small, + showstringspaces=false, + breaklines=true, + commentstyle=\color{green!50!black}, + stringstyle=\color{red}, + keywords={true, false, null}, + keywordstyle=\color{blue}\bfseries, + morestring=[s]{"}{"}, + morecomment=[l]{//}, + morecomment=[s]{/*}{*/}, +} diff --git a/projektdokumentation/Allgemein/Seitenstil.tex b/projektdokumentation/Allgemein/Seitenstil.tex new file mode 100644 index 0000000..fdf284b --- /dev/null +++ b/projektdokumentation/Allgemein/Seitenstil.tex @@ -0,0 +1,83 @@ +% !TEX root = ../Projektdokumentation.tex + +% Seitenränder ----------------------------------------------------------------- +\setlength{\topskip}{\ht\strutbox} % behebt Warnung von geometry +\geometry{a4paper,left=25mm,right=25mm,top=33mm,bottom=33mm,head=53.14769pt} +\deffootnote{1em}{1em}{\textsuperscript{\thefootnotemark}\fontsize{9pt}{11pt}\selectfont} + +\usepackage[ + automark, % Kapitelangaben in Kopfzeile automatisch erstellen + headsepline, % Trennlinie unter Kopfzeile + ilines % Trennlinie linksbündig ausrichten +]{scrlayer-scrpage} + +% Kopf- und Fußzeilen ---------------------------------------------------------- +\pagestyle{scrheadings} +% chapterpagestyle gibt es nicht in scrartcl +%\renewcommand{\chapterpagestyle}{scrheadings} +\clearpairofpagestyles + +% Kopfzeile +\renewcommand{\headfont}{\normalfont} % Schriftform der Kopfzeile +\ihead{\large{\textsc{\titel}}\\ \small{\untertitel} \\[2ex] \textit{\headmark}} +\chead{} +\ohead{\includegraphics[scale=0.09]{\betriebLogo}} +%\setheadwidth[0pt]{textwithmarginpar} % Kopfzeile über den Text hinaus verbreitern (falls Logo den Text überdeckt) + +% Fußzeile +\cfoot{} +\ofoot{\pagemark} + + +% Überschriften nach DIN 5008 in einer Fluchtlinie +% ------------------------------------------------------------------------------ + +% Abstand zwischen Nummerierung und Überschrift definieren +% > Schön wäre hier die dynamische Berechnung des Abstandes in Abhängigkeit +% > der Verschachtelungstiefe des Inhaltsverzeichnisses +\newcommand{\headingSpace}{1.5cm} + +% Abschnittsüberschriften im selben Stil wie beim Inhaltsverzeichnis einrücken +\renewcommand*{\othersectionlevelsformat}[3]{ + \makebox[\headingSpace][l]{#3\autodot} +} + +% Für die Einrückung wird das Paket tocloft benötigt +%\cftsetindents{chapter}{0.0cm}{\headingSpace} +\cftsetindents{section}{0.0cm}{\headingSpace} +\cftsetindents{subsection}{0.0cm}{\headingSpace} +\cftsetindents{subsubsection}{0.0cm}{\headingSpace} +\cftsetindents{figure}{0.0cm}{\headingSpace} +\cftsetindents{table}{0.0cm}{\headingSpace} + + +% Allgemeines +% ------------------------------------------------------------------------------ + +\setstretch{1.15} % Zeilenabstand 1.15 Zeilen +\frenchspacing % erzeugt ein wenig mehr Platz hinter einem Punkt + +\renewcommand{\rmdefault}{phv} % Arial als Std Schriftart +\renewcommand{\sfdefault}{phv} + +% Schusterjungen und Hurenkinder vermeiden +\clubpenalty = 10000 +\widowpenalty = 10000 +\displaywidowpenalty = 10000 + +% Quellcode-Ausgabe formatieren +\lstset{numbers=left, numberstyle=\tiny, numbersep=5pt, breaklines=true} +\lstset{emph={square}, emphstyle=\color{red}, emph={[2]root,base}, emphstyle={[2]\color{blue}}} + +\counterwithout{footnote}{section} % Fußnoten fortlaufend durchnummerieren +\setcounter{tocdepth}{3} % im Inhaltsverzeichnis werden die Kapitel bis zum Level der subsubsection übernommen +\setcounter{secnumdepth}{3} % Kapitel bis zum Level der subsubsection werden nummeriert + +% Aufzählungen anpassen +\renewcommand{\labelenumi}{\arabic{enumi}.} +\renewcommand{\labelenumii}{\arabic{enumi}.\arabic{enumii}.} +\renewcommand{\labelenumiii}{\arabic{enumi}.\arabic{enumii}.\arabic{enumiii}} + +% Tabellenfärbung: +\definecolor{heading}{rgb}{0.64,0.78,0.86} +\definecolor{odd}{rgb}{0.9,0.9,0.9} diff --git a/projektdokumentation/Allgemein/Silbentrennung.tex b/projektdokumentation/Allgemein/Silbentrennung.tex new file mode 100644 index 0000000..a6375b1 --- /dev/null +++ b/projektdokumentation/Allgemein/Silbentrennung.tex @@ -0,0 +1,3 @@ +% Trennvorschläge im Text werden mit \" angegeben +% untrennbare Wörter und Ausnahmen von der normalen Trennung können in dieser +% Datei mittels \hyphenation definiert werden diff --git a/projektdokumentation/Allgemein/natdin.bst b/projektdokumentation/Allgemein/natdin.bst new file mode 100644 index 0000000..18a3744 --- /dev/null +++ b/projektdokumentation/Allgemein/natdin.bst @@ -0,0 +1,2234 @@ +%% natdin.bst Vers. [3.1] 2006-01-02 mit PWD cite-multimode +%% +%% Aenderungen seit Vers. [3.0bx]: +%% a) Funktions-Zusaetze bei Autoren sind nun moeglich +%% b) Internetquellen ohne Zusatz "Online--Ressource" ! +%% c) verbesserte INPROCEEDINGS, PROCEEDINGS-Funktionen +%% d) neue TECHREPORT und MANUAL-Funktionen +%% e) neue format.pages.book-Funktion +%% f) neue format.online.lastchecked-Funktion +%% +%% +%% K.F.Lorenzen (Copyright 1994-2006) email: lorenzen.marxen@t-online.de +%% ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +%% Mit diesem BibTex-style werden Literaturverzeichnisse nach dem deutschen +%% Standard DIN 1505, Teil 2 und 3 formatiert. Die label im Quellen-/ +%% Literaturverzeichnis sind vom Typ 'Verfasser - Jahr' und +%% entsprechen den Zitatformen im Text. Es koennen alle von +%% Patrick W. Daly im natbib-Paket implementierten Zitierbefehle +%% genutzt werden. Eine Kurzbeschreibung liegt als Datei natnotes.pdf zusammen +%% mit der im Aufbau befindlichen Beschreibung des natdin-Stils +%% unter der URL http://www.haw-hamburg.de/pers/Lorenzen/bibtex. +%% Eine Anleitung zur Anwendung der Norm DIN 1505 findet sich unter der +%% URL http://www.bui.haw-hamburg.de/pers/klaus.lorenzen/ASP/litverz.pdf +%% +%% Es werden nun auch Elektronische Online / Offline Ressourcen wie +%% Internetquellen, CD-ROM usw. verarbeitet. Dazu kommen spezielle +%% Publikationsformen wie Patente, Normen, Karten, Fernsehaufzeichnungen, +%% Gesetzesstellen, Spiele u.a. +% +%% NATDIN.BST muss zusammen mit NATBIB.STY von Patrick W. Daly und der in +%% dieser Verteilung modifizierten Konfiguration NATBIB.CFG aufgerufen +%% werden. NATDIN.BST ist vorzugsweise in den ...\bibtex\bst - Pfad zu +%% stellen. Die jeweils individuelle Konfiguration NATBIB.CFG wird am +%% besten in das Verzeichnis der LaTex-Quelle selbst gelegt. +%% +%% Eine Muster-Konfiguration wird auf Vorschlag von Helge Baumann eingefuegt: +% ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +% NATBIB.CFG in Verbindung mit natbib.sty Lorenzen, 2005-05-31 +% diese Konfigurationsdatei wird zuletzt eingelesen und +% enthaelt die lokal gewuenschten Einstellungen fuer den +% Bibliographie-- und Zitierstil +% +% \newcommand{\bibstyle@natdin}% +% {\bibpunct{(}{)}{;}{a}{}{,~} +% \gdef\NAT@biblabelnum##1{\textbf{##1}\\}} %% \\ bewirkt Zeilenumbruch +% %% nach label-Ausgabe +% +% \bibstyle@natdin +% +% % Einzug der Belege nach der Einordnungsmarke +% \setlength{\bibhang}{7mm} +% +%% ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +%% +%% Zur Gewaehrleistung der Allgemeingueltigkeit von bib-files gelten +%% in den DIN-styles mit einer einzigen Ausnahme die in der Tex-Literatur +%% veroeffentlichten originalen Definitionen und Regeln fuer die +%% Publikationstypen (entry-types) und die bibliographischen Felder (fields). +%% Die Ausnahme bezieht sich auf den entry-type PROCEEDINGS und das Feld +%% address, fuer das Oren Patashnik dort die Sonderbedeutung "Konferenzort" +%% gewaehlt hatte. In den DIN-Stilen behaelt address auch hier seine normale +%% Bedeutung als "Verlagsort" bei! +%% In einigen entry-types werden zu Gunsten bibliographischer Vollstaendigkeit +%% mehr optionale Felder verwendet als seinerzeit von Patashnik angegeben. +%% Treten "Schoenheitsfehler" im fertig gesetzten output auf, +%% lassen sich diese so gut wie immer durch eine veraenderte +%% Erfassung im bib-inputfile beseitigen. Oren Patashnik empfiehlt, die +%% Definition der Felder weit auszulegen. Last but not least koennen +%% Sie im output-file < *.bbl > noch letzte Hand zur Korrektur ansetzen. +%% Wegen der meist langen Internetadressen kann es zu "unschoenen" Luecken +%% im Belegtext kommen. Dann muss mit \underfull hbox.... gekaempft werden. +%% +%% Die Darstellung von Internetadressen wird durch das Zusatzpaket +%% url.sty ver 3.1, 15-Mar-2004 von Donald Arseneau erheblich verbessert und +%% wird sehr empfohlen. +%% +%% HYPERREF-Paket: wird dieses Paket zusaetzlich geladen, werden im output +%% aktivierbare externe (URLs usw.) und interne (Dokumentbezogene) Links +%% gesetzt. Sehr gute Zusammenarbeit mit PDFLaTex u.a. +% +%% WARN- UND FEHLERMELDUNGEN +%% Ursache von Warnmeldungen sind meistens ausgelassene Felder oder +%% Erfassungs-"Fehler". Letztere haengen teilweise mit den gegenueber US- +%% Gepflogenheiten andersartigen bibliographischen Regeln nach DIN 1505 +%% zusammen. Sie sind also in Wahrheit keine "Fehler" und duerfen fast immer +%% ignoriert werden. Dennoch pruefen Sie diese Warnungen, um heraus zu finden, +%% ob Publikationstyp (=entry-type) und "fields" eventuell unzulaessig +%% kombiniert worden sind. +%% Echte Fehler ("errors") duerften nur noch bei bibliographisch falscher +%% Erfassung auftreten. Pruefen Sie die Syntax, den entry-type und die fields. +%% Zu guter letzt: Qualitaetsmasstab ist einzig der DIN-konforme output! +%% +%% DANKSAGUNG +%% Hartmut Lueddecke, HAW Hamburg, hat viele Verbesserungsvorschlaege +%% in die frueheren Versionen eingebracht. Ihm danke ich herzlich. +%% Patrick W. Daly, dem Entwickler des Natbib-Stils, verdanke ich viele +%% Anregungen und den steten Ansporn, die DIN-Stile zu verbessern. +%% Helge Baumann hatte mit seiner Weiterentwicklung meines alten natdin.bst +%% zu dinat.bst (Vers. 2001) die volle Zitierkapazitaet des natbib-Pakets +%% eingefuehrt. Damit war ein deutlicher Zugewinn erreicht. Von ihm stammt +%% der in der vorliegenden natdin.bst-Version 3.0x neu eingefuehrte Befehl +%% \dinatlabel unter seinem von H. Baumann vergebenen Namen. +%% Viele an dieser Stelle ungenannt bleibende Anwender haben mich in +%% den vergangenen Jahren auf Fehler oder Verbesserungsmoeglichkeiten +%% aufmerksam gemacht und so diesen Stil mitentwickelt. Ihnen gilt mein +%% besonderer Dank. Ihr Feedback ist immer willkommen und eine Ermunterung. +%% +%% Klaus F. Lorenzen +%% +%% ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +%% version 0.99c for BibTeX versions 0.99c or later, LaTeX2e version +%% Copyright (C) 1985, all rights reserved. +%% Copying of this file is authorized only if either +%% (1) you make absolutely no changes to your copy, including name, or +%% (2) if you do make changes, you name it something other than +%% natdin.bst, natbib.sty +%% This restriction helps ensure that all standard styles are identical. +%% ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +%% +%% NEUE FELDER +%% Zur Erfassung von Internetquellen, E-mail u.a. gibt es folgende +%% neue Felder: doi, lastchecked (nach Gray), url, urn +%% Internetquellen (z.B. auch E-mail) werden vorzugsweise mit dem +%% BOOKLET-Typ erfasst. +%% Normen, Patente, Schutzrechte, Gesetzesstellen sind mit dem MISC-Typ +%% zu erfassen. +%% + + +ENTRY + { address %% Verlagsort (immer!) + author %% persoenlicher Urheber eines Werkes oder am Zustandekommen + %% beteiligte Personen(=Mitarbeiter, Uebersetzer, Redakteur u.a.) + booktitle %% a) Gesamttitel eines mehrbaendigen Werkes +% %% b) Titel des Sammelwerks, das einzelne selbstaendige +% %% Beitraege mit eigenem Titel enthaelt ->incollection + chapter %% Kapitel in einem Buch (Monographie) + doi %%% Digital Object Identifier ->article + edition %% a) Auflagevermerk +% %% b) bei selbst. elektron. Quellen == Version ->booklet + editor %% Persoenl.Herausgeber oder Koerperschaftlicher Herausgeber + howpublished %% beliebiger Verlegervermerk: veroeffentlicht "von wem, wo" + institution %% Institution, die e.verlagsfreie Veroeffentlichung betreibt + isbn %% Standardnr fuer Buecher + issn %% - " - : Zeitschriften u. Serien + journal %% Titel einer Zeitschrift + key %% Zusaetzlich vergebener Sortierschluessel, mitunter notwend. + lastchecked %% neues Feld fuer das Datum des Online-Abrufs +% %% einer Internetquelle (n. GRAY ) + month %% naehere Bestimmung des Erscheinungsjahres -> macro 's + note %% freies Eingabefeld fuer zusaetzliche Informationen z. Quelle + number %% Versch. Bedeutungen in Abhaengigkeit vom Eingabetyp: +% %% a) Bandnummer einer gezaehlten Reihe (series) +% %% b) Heftnummer einer Zeitschrift ->article +% %% c) Nummer eines Forschungsberichts ->techreport + organization %% a) Name der Organisation/des Organisators e. Tagung,Konferenz +% %% b) Name einer Firma/Gesellschaft, die ein ->manual herausgab + pages %% Umfangsangaben, meist Seitenzahlen + publisher %% Verlag + school %% Hochschule/Universitaet, die eine Dipl.-Arb./Dissertation veroeff. + series %% Titel e.Reihe, in der ein best. Buchtitel erschienen ist + title %% Titel einer (namentlich gekennzeichneten) Veroeffentlichung + type %% Zusatzfeld z.Kennzeichnung e.besonderen Publikationstyps + url %% neues Feld URL ( Uniform Resource Locator ): +% %% Serveradresse einer Internetquelle + urn %% neues Feld URN ( Uniform Resource Name ): +% %% Persistent Identifier einer Internetquelle + volume %% a) Zaehlung bei einem mehrbaendigen Werk ->book/->proceedings +% %% b) Jahrgang einer Zeitschrift ->article + year %% Erscheinungsjahr + } + {} + { label extra.label sort.label short.list dinat.label} + +%%%---------------------------------------------------------------------------- +% Einige Standardvorgaben, die vom Benutzer veraendert werden koennen. +%%%---------------------------------------------------------------------------- + +% Abkuerzung ("... und andere") bei Mehrverfasserquellen: + +FUNCTION { ua.etal } { " u.\,a." } %% evtl. auch in eckigen Klammern " [u.\,a.]" + +%% oder lateinisch: FUNCTION { ua.etal } { " et~al." } + +FUNCTION { und } { " und " } + +%% oder ausgeschrieben: FUNCTION { und } { " und " } + + +% Einige elektronische Medien erhalten nach DIN 1505 eine "Ergaenzende Angabe" +% zusaetzlich zum materiellen Typ, z.B. CD ROM oder DVD u.a.: + +FUNCTION { eress } { "Elektronische Ressource" } +%%%----------------------------------------------------------------------------------- + +INTEGERS { output.state before.all mid.sentence after.sentence after.block } + +INTEGERS { after.firstblock } + +INTEGERS { colon.after period.dash } + +INTEGERS { zahl lang } + +INTEGERS { len } + +INTEGERS { longest.label.width last.extra.num number.label } + +INTEGERS { nameptr namesleft numnames } + +INTEGERS { ptr collation collrest } + +STRINGS { longest.label last.label next.extra } + +STRINGS { h s t u v w } + +STRINGS { fkt } + +%%--------------------------- + +FUNCTION {init.state.consts} +{ #0 'before.all := + #1 'mid.sentence := + #2 'after.sentence := + #3 'after.block := + #4 'colon.after := + #5 'period.dash := + #6 'after.firstblock := +} + + + +FUNCTION {set.period.dash} %% Wenn ein ". - " die Satzteile trennen soll.! +{ output.state before.all = + 'skip$ + { period.dash 'output.state := } + if$ +} + + +FUNCTION {set.period.dash.check} +{ empty$ + 'skip$ + 'set.period.dash + if$ +} + +FUNCTION {set.colon.after} %%$$$ Wenn ein ": " d. Satzteile trennen soll! +{ output.state before.all = + 'skip$ + { colon.after 'output.state := } + if$ +} + +%% Wenn ein " " die Satzteile trennen soll.! +FUNCTION {new.sentence} +{ output.state before.all = + 'skip$ + { after.sentence 'output.state := } + if$ +} + +%% neu 17/2/94 Wenn ein ", " die Satzteile trennen soll.! +FUNCTION { part.of.sentence } +{ output.state before.all = + 'skip$ + { mid.sentence 'output.state := } + if$ +} + +FUNCTION {new.sentence.checka} +{ empty$ + 'skip$ + 'new.sentence + if$ +} + +FUNCTION {output.nonnull} +{ 's := + output.state after.block = + { add.period$ write$ + newline$ + "\newblock " write$ + } + { output.state before.all = + { write$ } + { output.state colon.after = + { ": " * write$ + newline$ + "\newblock " write$ + } + { output.state period.dash = + { ". -- " * write$ + newline$ + "\newblock " write$ + } + { output.state mid.sentence = + { ", " * write$ } + { output.state after.sentence = + { " " * write$ } + { output.state after.firstblock = + { add.period$ write$ + newline$ + "\newblock " write$ + } + { write$ + newline$ + "\newblock " write$ + } + if$ + } + if$ + } + if$ + } + if$ + } + if$ + } + if$ + after.block 'output.state := + } + if$ + s + } + +FUNCTION {output} +{ duplicate$ empty$ + 'pop$ + 'output.nonnull + if$ +} + +FUNCTION {output.check} +{ 't := + duplicate$ empty$ + { pop$ "empty " t * " in " * cite$ * warning$ } + 'output.nonnull + if$ +} + +FUNCTION {new.block} +{ output.state before.all = + 'skip$ + { after.block 'output.state := } + if$ +} + +FUNCTION {not} +{ { #0 } + { #1 } + if$ +} + +FUNCTION {and} +{ 'skip$ + { pop$ #0 } + if$ +} + +FUNCTION {or} +{ { pop$ #1 } + 'skip$ + if$ +} + +FUNCTION {format.date} +{ year duplicate$ empty$ + { "empty year in " cite$ * warning$ + pop$ "" } + 'skip$ + if$ + month empty$ + 'skip$ + { type$ "book" = + type$ "inbook" = + OR + 'skip$ + { month " " * swap$ * } + if$ + } + if$ +%% ohne Extrabuchstabe beim Erscheinungsjahr +} + +FUNCTION {format.edition.or.date} +{ edition empty$ year empty$ and + { "" } + { edition empty$ + { type empty$ NOT + { type #-1 #4 substring$ "mail" = + type #1 #4 substring$ "Mail" = + OR + { " gesendet: " "-- " type * swap$ * + format.date * + } + { "\,Version:\," + format.date * + } + if$ + } + { "\,Version:\," + format.date * + } + if$ + } + { year empty$ + { "\,Version:\," edition * } + { "\,Version:\," edition * ", " * + format.date * + } + if$ + } + if$ + } + if$ +} + +FUNCTION {format.online.lastcheck} +{ lastchecked empty$ + { url empty$ doi empty$ urn empty$ and and + { skip$ } + { "" output } + if$ + } + { url empty$ doi empty$ urn empty$ and and + { "there's a lastchecked date but no url, urn or doi in " + cite$ * warning$ + } + { part.of.sentence + lastchecked "Abruf: " swap$ * output + } + if$ + } + if$ +} + +FUNCTION {format.maillist.lastcheck} + { type empty$ NOT + { type #-1 #4 substring$ "mail" = + type #1 #4 substring$ "Mail" = + OR + { format.online.lastcheck } + 'skip$ + if$ + } + 'skip$ + if$ + } + +FUNCTION {format.doi} +{ doi empty$ + { "" } + { new.block "\url{http://dx.doi.org/" doi * "}" * + %% { new.block "\url{http://dx.medra.org/" doi * "}" * + } + if$ +} + +FUNCTION {format.url} +{ urn missing$ + { doi missing$ + { url empty$ + { "" } + { type empty$ NOT + { type #-1 #4 substring$ "mail" = + type #1 #4 substring$ "Mail" = + OR + { type$ "incollection" = + { "" } + { "\,Absenderadresse: \url{" url * "}" * } + if$ + } + { "\url{" url * "}" * }%% evtl. "URL" oder "<...>" + if$ + } + { "\url{" url * "}" * } %% evtl. "URL" oder "<...>" + if$ + } + if$ + } + { format.doi } + if$ + } + { "\url{http://nbn-resolving.de/urn/resolver.pl?urn=" urn * "}" * + } + if$ +} + +FUNCTION {format.maillist.url} + { url empty$ + { "" } + { type empty$ + { "" } + { type #-1 #4 substring$ "mail" = + type #1 #4 substring$ "Mail" = + OR + { "\url{" url * "}" * } + { "" } + if$ + } + if$ + } + if$ + } + + +FUNCTION {format.full.names} +{'s := + #1 'nameptr := + s num.names$ 'numnames := + numnames 'namesleft := + { namesleft #0 > } + { s nameptr + "{vv~}{ll}" format.name$ 't := + nameptr #1 > + { + namesleft #1 > + { ", " * t * } + { + numnames #2 > + { "," * } + 'skip$ + if$ + t "others" = + { ua.etal * } + { und * t * } + if$ + } + if$ + } + 't + if$ + nameptr #1 + 'nameptr := + namesleft #1 - 'namesleft := + } + while$ +} + +FUNCTION {author.editor.full} +{ author empty$ + { editor empty$ + { "" } + { editor format.full.names } + if$ + } + { author format.full.names } + if$ +} + +FUNCTION {author.full} +{ author empty$ + { "" } + { author format.full.names } + if$ +} + +FUNCTION {editor.full} +{ editor empty$ + { "" } + { editor format.full.names } + if$ +} + +FUNCTION {make.full.names} +{ type$ "book" = + type$ "inbook" = + or + 'author.editor.full + { type$ "proceedings" = + 'editor.full + 'author.full + if$ + } + if$ +} + + +FUNCTION {output.bibitem} +{ newline$ + "\bibitem[\scshape{}" write$ + label write$ + ")" make.full.names duplicate$ short.list = + { pop$ } + { * } + if$ + "]{" * write$ + cite$ write$ + "}" write$ + newline$ + "\dinatlabel{\upshape{}" dinat.label * "} " * %% Zeilenumbruch steht in NATBIB.CFG! + write$ + "" + before.all 'output.state := +} + + +FUNCTION {fin.entry} +{ write$ + newline$ +} + +FUNCTION {field.or.null} +{ duplicate$ empty$ + { pop$ "" } + 'skip$ + if$ +} + +FUNCTION {emphasize} +{ duplicate$ empty$ + { pop$ "" } + { "\emph{" swap$ * "}" * } + if$ +} + +FUNCTION { capitalize } + { duplicate$ empty$ + { pop$ "" } + { "\textsc{" swap$ * "}" * } + if$ +} + +FUNCTION {article.in.journal} +{ duplicate$ empty$ + { pop$ "" } + { author missing$ title missing$ and + { emphasize } + { emphasize "{In: }" swap$ * } + if$ + } + if$ +} + +FUNCTION {format.names} +{ 's := + "" 'u := + #1 'nameptr := + s num.names$ 'numnames := + numnames 'namesleft := + { namesleft #0 > } + { + s nameptr "{ll}" format.name$ 't := + t capitalize 't := + s nameptr "{ jj}" format.name$ 'w := + s nameptr "{, ff}{ vv}{ jj}" format.name$ 'u := + u text.length$ 'lang := + #1 'zahl := + "" 'v := + { zahl lang < } + { u zahl #1 substring$ "~" = + { v "" = + { u #1 zahl #1 - substring$ 'v := } + 'skip$ + if$ + v u zahl #2 substring$ * "." * w * 'v := + } + 'skip$ + if$ + zahl #1 + 'zahl := } + while$ + v "" = + { u 'v := } + 'skip$ + if$ + t v * fkt * 't := + nameptr #1 > + { namesleft #1 > + { " ; " * t * } + { numnames #2 > + { " " * } + 'skip$ + if$ + t "\textsc{others}" = t "\textsc{others} (Hrsg.)" = or + { ua.etal * } + { " ; " * t * } + if$ + } + if$ %% Ende der namesleft-Pruefung + } + 't + if$ %% Ende der nameptr-Pruefung + + nameptr #1 + 'nameptr := + namesleft #1 - 'namesleft := + } + while$ %% Ende von { namesleft #0 > } ganz oben + "" 'fkt := %% fkt wird zurueckgesetzt +} + +FUNCTION {format.authors} +{ author empty$ + { "" } + { author format.names } + if$ +} + +FUNCTION {format.editors} +{ editor empty$ + { author empty$ + { "empty author and editor in " cite$ * warning$ "" } + 'skip$ + if$ + } + { author empty$ + { " (Hrsg.)" 'fkt := + editor format.names + } + { " (Hrsg.)" 'fkt := + " ; " * editor format.names * + } + if$ + } + if$ + } + +%% 2005-11-11 +FUNCTION { format.authors.organization } +{ type$ "misc" = + { organization empty$ + { author empty$ + { "" } + { author format.names " (Erfinder)" * } + if$ + } + { author empty$ + { organization } + { author format.names " (Erfinder); " * + organization * " (Anmelder)" * + } + if$ + } + if$ + } + { type$ "manual" = + { organization empty$ + { format.authors } + { author empty$ + { organization capitalize " (Hrsg.)" * } + { author format.names } + if$ + } + if$ + } + 'skip$ + if$ + } + if$ +} + +FUNCTION { format.editors.organization } +{ organization empty$ + 'skip$ + { type$ "misc" = + { organization } + { * " ; " * organization " (Veranst.)" *} + if$ + } + if$ +} + +FUNCTION { format.tr.institution } +{ institution empty$ + 'skip$ + { institution capitalize } + if$ +} + +FUNCTION {format.ed.incoll} +{ editor empty$ + { "" } + { " (Hrsg.)" 'fkt := + editor format.names + format.editors.organization + } + if$ +} + + +FUNCTION {format.title} +{ title empty$ + { "" } + { title } %% Text so wie er dasteht im Feld title + if$ +} + +FUNCTION {format.number} +{ number empty$ + { "" } + { number " " * } %% Text so wie er im Feld number steht plus " " + if$ +} + +FUNCTION {format.digital.type} +{ type empty$ + { "" } + { type #-1 #4 substring$ "mail" = + type #1 #4 substring$ "Mail" = + OR + { "" } + { type } %% Typ einer digitalen Ressource in Form einer + %% "Ergaenzenden Angabe", so wie er dasteht; + %% Alternativ kann dieser Text auch in NOTE erfasst werden. + if$ + } + if$ +} + +FUNCTION {n.dashify} +{ 't := + "" + { t empty$ not } + { t #1 #1 substring$ "-" = + { t #1 #2 substring$ "--" = not + { "--" * + t #2 global.max$ substring$ 't := + } + { { t #1 #1 substring$ "-" = } + { "-" * + t #2 global.max$ substring$ 't := + } + while$ + } + if$ + } + { t #1 #1 substring$ * + t #2 global.max$ substring$ 't := + } + if$ + } + while$ +} + +%% Auflagenvermerke gibt man komplett, einschliesslich Abkuerzungen in +%% das Feld edition ein: ---> EDITION= { 3., erw. und verb. Aufl. } +%% oder fremdsprachlich: EDITION= { 2nd edition } + +FUNCTION {format.edition} +{ edition empty$ + { "" } + { edition } + if$ +} + +FUNCTION {format.version.url} +{ url empty$ doi empty$ urn empty$ and and + { type$ "techreport" = + { format.edition } + { "" } + if$ + } + { format.edition.or.date output format.url } + if$ +} + +FUNCTION {format.edition.or.version} +{ url empty$ doi empty$ urn empty$ and and + { format.edition } + { format.edition.or.date } + if$ +} + +FUNCTION {format.address.publisher.year} +{ publisher empty$ + { address empty$ + { year empty$ + { "" } + { year } + if$ + } + { "there's an address but no publisher in " cite$ * warning$ + address ", " * format.date * + } + if$ + } + { address empty$ + { year empty$ + { "neither address nor publication date in " cite$ * warning$ + publisher + } + { publisher ", " * format.date * } + if$ + } + { year empty$ + { address " : " * publisher * } + { address " : " * publisher * ", " * format.date * } + if$ + } + if$ + } + if$ +} + +FUNCTION {format.howpublished} +{ url missing$ urn missing$ doi missing$ AND AND + { howpublished empty$ + { address empty$ + { type empty$ + { "" } + { type #-1 #4 substring$ "mail" = + { "(gesendet: " new.sentence + format.date * ")" * + } + { "" } + if$ + } + if$ + format.date * + } + { address ", " * format.date * } + if$ + } + { address empty$ + { howpublished ", " * format.date * } + { address " : " * howpublished * ", " * format.date * } + if$ + } + if$ + } + { howpublished empty$ + { "" } + { howpublished } + if$ + } + if$ +} + +FUNCTION {format.btitle} +{ title emphasize +} + +FUNCTION {tie.or.space.connect} +{ duplicate$ text.length$ #3 < + { "~" } + { " " } + if$ + swap$ * * +} + +FUNCTION {format.btitle.vol} +{ number empty$ + { series empty$ + { volume empty$ + { title emphasize } + { title emphasize ". Bd." * volume tie.or.space.connect } + if$ + } + { volume empty$ + { title emphasize }%% ein Buch, das zusaetzl. SERIES=Reihentitel besitzt + %% jetzt kommt d. Fall des mehrbaendigen Werkes mit Gesamttitel=SERIES + %% Zaehlung=VOLUME und Bandtitel=TITLE; + { series emphasize ". Bd." * volume tie.or.space.connect + ": " * "{\emph{" * title * "}}" * } + if$ + } + if$%% series-test + } + { title emphasize }%% wenn number ex., dann immer title-Feld + if$%% Ende number-test +} + +FUNCTION {format.series.number.din} +{ volume empty$ + { number empty$ + { series empty$ + { "" } + { "(" series * ")" * } %% d. Seriennr koennte auch gleich hier + %% im SERIES-Feld miterfasst werden + if$ + } + { series empty$ + { "(" number tie.or.space.connect ")" * + "there's a number but no series in " cite$ * warning$ + } + { "(" series * number tie.or.space.connect ")" * } + if$ + } + if$ + } + { series empty$ + { "" } + { type$ "proceedings" = %% Sonderfall, es darf VOLUME und NUMBER ex. ! + type$ "inproceedings" = OR + { number empty$ + { "(" series * ")" * } + { "(" series * number tie.or.space.connect ")" * } + if$ + } + { "" }%% Ausstieg mit Nullstring, s. Kommentar + if$ + }%% bei gezaehlten Reihen MUSS die Reihennr. im Feld NUMBER stehen! + if$ %% wenn also d. Feld VOLUME nicht leer ist, dann liegt ausser bei + %% Typ PROCEEDINGS/INPROCEEDINGS falsche + } %% Erfassung vor und es erfolgt d. Ausstieg mit d. Nullstring! + if$ +} + +FUNCTION {format.tr.series.or.number} +{ number empty$ + { series empty$ + { "" } + { "(" series * ")" * } + if$ + } + { series empty$ + { "(" number * ")" * } + { "(" series * number tie.or.space.connect ")" * } + if$ + } + if$ + } + +FUNCTION {format.misc.series} +{ series empty$ + { "" } + { "(" series * ")" * } + if$ +} + +FUNCTION { format.doi.urn } +{ urn empty$ + { doi empty$ + { "" } + { "DOI" doi n.dashify tie.or.space.connect } + if$ + } + { "URN" urn n.dashify tie.or.space.connect } + if$ +} + +FUNCTION { format.isbn.issn } +{ isbn empty$ + { issn empty$ + { "" } + { "ISSN" issn n.dashify tie.or.space.connect } + if$ + } + { "ISBN" isbn n.dashify tie.or.space.connect } + if$ +} + +FUNCTION {format.pages} +{ pages empty$ + { "" } + { url empty$ + { "S." pages n.dashify tie.or.space.connect } + { pages } + if$ + } + if$ +} + +FUNCTION {format.pages.book} +{ pages empty$ + { "" } + { note empty$ isbn empty$ AND + { "" pages n.dashify tie.or.space.connect " S" * + add.period$ + } + { "" pages n.dashify tie.or.space.connect " S" * + } + if$ + } + if$ +} + +FUNCTION {format.pages.bkcollation} +{ pages empty$ + { "" } + { "" pages n.dashify tie.or.space.connect } + if$ +} + +FUNCTION {format.bkpages.collat.check} +{ 's := + #1 'ptr := + s text.length$ 'collation := + collation #1 = + { format.pages.book } + { + collation 'collrest := + { collrest #0 > } + { s ptr #2 substring$ 't := + t "S." = + { format.pages.bkcollation + #0 'collrest := } + { ptr #1 + 'ptr := + collrest #1 - 'collrest := + #1 collrest = + { format.pages.book } + { skip$ } + if$ + } + if$ + } + while$ + } + if$ +} + +FUNCTION {format.vol.year.num.pages} +{ volume field.or.null + year empty$ + { "there's no year in " cite$ * warning$ } + { " (" year * ")" * * } + if$ + month empty$ + 'skip$ + { ", " month * * } + if$ + number empty$ + 'skip$ + { ", Nr. " number * * } + if$ + pages empty$ + 'skip$ + { duplicate$ empty$ + { pop$ "" } + { title missing$ + { ", " pages format.bkpages.collat.check * *} + { ", " format.pages * *} + if$ + } + if$ + } + if$ +} + +FUNCTION {format.chapter.inbook} +{ duplicate$ empty$ + { pop$ "empty chapter in " cite$ * warning$ } + { type empty$ + { "\emph{Kapitel\/} " swap$ tie.or.space.connect } + { type " " * swap$ * }%% wenn keine bes. Abschnittsform gen. werden soll, + %% koennte e. kl. Zwischenraum gewaehlt werden, z.B. " \, " + if$ + } + if$ +} + +FUNCTION {format.chapter.pages} +{ chapter empty$ + 'format.pages + { type empty$ + { "Kapitel " } + { url empty$ + { type } + { "Kapitel " } + if$ + } + if$ + chapter tie.or.space.connect + pages empty$ + 'skip$ + { ", " * format.pages * } + if$ + } + if$ +} + +FUNCTION {format.in.ed.booktitle.din} +{ booktitle empty$ + { "" } + { editor empty$ + { volume empty$ + { "{In: }" booktitle emphasize * }%% n. Belieben fettes In: + { "{In: }" booktitle emphasize * %% - " - + " Bd." volume tie.or.space.connect * + } + if$ + } + { volume empty$ + { "{In: }" format.ed.incoll * ": " * booktitle emphasize * } + { "{In: }" format.ed.incoll * ": " * booktitle emphasize * + " Bd." volume tie.or.space.connect * + } + if$ + } + if$ + } + if$ +} + +FUNCTION {format.thesis.tr.type} +{ type empty$ + 'skip$ + { pop$ + type + } + if$ +} + +FUNCTION {format.article.crossref} +{ key empty$ + { journal empty$ + { "need key or journal for " cite$ * " to crossref " * crossref * + warning$ + "" + } + { "{In: }{\emph " journal * "}" * }%% n. Belieben fettes In: + if$ + } + { "{In: }" key * }%% s.o. fettes In: + if$ + " {\textbf{\citep{" * crossref * "}" * "}" * "}" * ", " * format.pages * +} + +FUNCTION {format.crossref.editor} +%vorher,Lue { editor #1 "{vv~}{ll}" format.name$ " (Hrsg.)" * + { editor #1 "{ll}" format.name$ " (Hrsg.)" * + editor num.names$ duplicate$ + #2 > + { pop$ ua.etal * } + { #2 < + 'skip$ + { editor #2 "{ff }{vv }{ll}{ jj}" format.name$ "others" = + { ua.etal * } + { " ; " * editor #2 "{vv~}{ll}" format.name$ * " (Hrsg.)" * } + if$ + } + if$ + } + if$ +} + +FUNCTION {format.inbk.vol.title} +{ volume empty$ + { " In: " } + { title empty$ + { " In: Bd." volume tie.or.space.connect + " von " * + } + { "In: Bd." volume tie.or.space.connect ": " * title emphasize * + " (" * year * ") in " * + } + if$ + } + if$ + } + +FUNCTION {format.book.crossref} +{ type$ "inbook" = + { format.inbk.vol.title } + { volume empty$ + { "empty volume in " cite$ * "'s crossref of " * crossref * warning$ + " " + } + { ". -- Bd." volume tie.or.space.connect + " von " * + } + if$ + } + if$ + editor empty$ + editor field.or.null author field.or.null = + or + { key empty$ + { series empty$ + { "need editor, key, or series for " cite$ * " to crossref " * + crossref * warning$ + "" * + } + { "" * }%% dadurch kommt nach der Band Nr. gleich das label 2/6/99 +%% { "{\emph{" * series * "}} {\textbf{siehe}} " * } + if$ + } + { key * } + if$ + } + { "" * } + if$ + "{\textbf{\citep{" * crossref * "}" * "}" * "}" * +} + +FUNCTION {format.incoll.inproc.crossref} +{ editor empty$ + editor field.or.null author field.or.null = + or + { key empty$ + { booktitle empty$ + { "need editor, key, or booktitle for " cite$ * " to crossref " * + crossref * warning$ + "" + } + { "{In: }{\emph " booktitle * "}" * }%% + if$ + } + { "{In: }" } + if$ + } + { "{In: }" } + if$ + "{\textbf{\citep{" * crossref * "}" * "}" * "}" * %% ", " * format.pages * +} + +FUNCTION {article} +{ output.bibitem + format.authors "author" output.check + set.colon.after + format.title "title" output.check + crossref missing$ + { journal article.in.journal output.nonnull + new.sentence + format.vol.year.num.pages output + format.url output + } + { format.article.crossref output.nonnull } + if$ + format.online.lastcheck + doi set.period.dash.check + urn set.period.dash.check + format.doi.urn output + issn set.period.dash.check + format.isbn.issn output + note set.period.dash.check + note output + fin.entry +} + +FUNCTION {book} +{ output.bibitem + author empty$ + { format.editors "author and editor" output.check } + { format.authors format.editors output.nonnull } + if$ + set.colon.after + crossref missing$ + { format.btitle.vol "title" output.check } + { format.btitle "title" output.check } + if$ + format.edition output + format.address.publisher.year "publisher" output.check + new.sentence + crossref missing$ + { format.series.number.din output } + { format.book.crossref output.nonnull } + if$ + pages empty$ + { skip$ } + { pages set.period.dash.check + pages format.bkpages.collat.check output + } + if$ + format.doi output + format.url output + new.block + isbn set.period.dash.check + format.isbn.issn output + note set.period.dash.check + note output + fin.entry +} + +FUNCTION {booklet} +{ output.bibitem + author empty$ + { format.editors "author and editor" output.check } + { format.authors format.editors output.nonnull } + if$ + set.colon.after + format.btitle "title" output.check + format.edition.or.version output + format.url output + format.online.lastcheck + format.howpublished output + series new.sentence.checka + format.series.number.din output + pages empty$ + { skip$ } + { pages set.period.dash.check + pages format.bkpages.collat.check output + } + if$ + type set.period.dash.check + format.digital.type output + doi set.period.dash.check + urn set.period.dash.check + format.doi.urn output + note set.period.dash.check + note output + format.isbn.issn output + fin.entry +} + +FUNCTION {inbook} +{ output.bibitem + chapter format.chapter.inbook output.nonnull + crossref missing$ + { author empty$ + { format.editors "\,{In:\,}" swap$ * "author and editor" output.check } + { format.authors "\,{In:\,}" swap$ * output.nonnull } + if$ + author empty$ editor empty$ AND + { before.all 'output.state := } + { set.colon.after } + if$ + format.btitle.vol "title" output.check + format.edition output + format.address.publisher.year "publisher" output.check + new.sentence + format.series.number.din output + isbn set.period.dash.check + format.isbn.issn output + } + { format.book.crossref output.nonnull + } + if$ + part.of.sentence + format.pages output + note set.period.dash.check + note output + fin.entry +} + +FUNCTION {incollection} +{ output.bibitem + format.authors "author" output.check + set.colon.after + format.title "title" output.check + format.version.url output + type empty$ NOT + { type #-1 #4 substring$ "mail" = + type #1 #4 substring$ "Mail" = + OR + 'skip$ + { format.online.lastcheck } + if$ + } + { format.online.lastcheck } + if$ + crossref missing$ + { format.in.ed.booktitle.din "booktitle" output.check + format.edition output + format.address.publisher.year "publisher" output.check + format.maillist.url output + format.maillist.lastcheck + new.sentence + format.series.number.din output + doi set.period.dash.check + urn set.period.dash.check + format.doi.urn output + isbn set.period.dash.check + issn set.period.dash.check + format.isbn.issn output + } + { format.incoll.inproc.crossref output.nonnull } + if$ + part.of.sentence + format.chapter.pages "pages" output.check + note set.period.dash.check + note output + fin.entry +} + +FUNCTION {inproceedings} +{ output.bibitem + format.authors "author" output.check + set.colon.after + format.title "title" output.check + crossref missing$ + { format.in.ed.booktitle.din "booktitle" output.check + address empty$ + { organization new.sentence.checka + organization output + part.of.sentence + format.address.publisher.year output + } + { format.address.publisher.year "publisher" output.check } + if$ + new.sentence + format.series.number.din output + isbn set.period.dash.check + issn set.period.dash.check + format.isbn.issn output + } + { format.incoll.inproc.crossref output.nonnull } + if$ + part.of.sentence + format.pages output + note set.period.dash.check + note output + fin.entry +} + +FUNCTION {conference} { inproceedings }%% nach Patashnik, wg US-Kompatibilitaet + +FUNCTION {manual} +{ output.bibitem + author empty$ + { organization empty$ + { title empty$ + 'skip$ + {format.btitle "title" output.check } + if$ + } + 'skip$ + if$ + } + 'skip$ + if$ + format.authors.organization output.nonnull + set.colon.after + format.btitle "title" output.check + format.edition "edition" output.check + author empty$ organization empty$ AND + { address "address" output.check + part.of.sentence + } + { organization empty$ + { address "address" output.check + part.of.sentence + } + { address ": " * organization * output + part.of.sentence + } + if$ + } + if$ + format.date output + number empty$ + 'skip$ + { "(" number * ") " * output } + if$ + pages empty$ + { skip$ } + { pages set.period.dash.check + pages format.bkpages.collat.check output + } + if$ + format.doi output + format.url output + format.online.lastcheck + note set.period.dash.check + note output + fin.entry +} + +%% Standard ist "Diplomarbeit", anderes mit TYPE="anderer Typ" erfassen! +%% z.B. TYPE={Hausarbeit}, TYPE={Diss.}, TYPE={Habil.}, TYPE={Magisterarb.} +FUNCTION {mastersthesis} +{ output.bibitem + format.authors "author" output.check + set.colon.after + format.btitle "title" output.check + address output + part.of.sentence + school "school" output.check + part.of.sentence + "Diplomarbeit" format.thesis.tr.type output.nonnull + part.of.sentence + format.date "year" output.check + format.url output + format.online.lastcheck + doi set.period.dash.check + urn set.period.dash.check + format.doi.urn output + pages empty$ + { skip$ } + { pages set.period.dash.check + pages format.bkpages.collat.check output + } + if$ + note set.period.dash.check + note output + fin.entry +} + +FUNCTION {phdthesis} %% {mastersthesis}% ist identisch bis auf Standardwert, s.o. +{ output.bibitem + format.authors "author" output.check + set.colon.after + format.btitle "title" output.check + address output + part.of.sentence + school "school" output.check + part.of.sentence + "Diss." format.thesis.tr.type output.nonnull % koennte auch `Dissertation' sein + part.of.sentence + format.date "year" output.check + format.url output + format.online.lastcheck + doi set.period.dash.check + urn set.period.dash.check + format.doi.urn output + pages empty$ + { skip$ } + { pages set.period.dash.check + pages format.bkpages.collat.check output + } + if$ + note set.period.dash.check + note output + fin.entry +} + + +%% Normen, Vornormen, Schutzrechte (Patente) werden hiermit erfasst; +%% E-mail, (auch Internetressourcen moeglich; besser --> booklet-Fkt!) +%% das type-Feld uebernimmt eine wichtige Steuerfunktion: +FUNCTION {misc} +{ output.bibitem + type missing$ not + { type duplicate$ #1 #4 substring$ "Norm" = + type #1 #4 substring$ "Vorn" = OR + { " " * + format.number * output + new.sentence + format.date output + title empty$ + { skip$ } + { add.period$ new.sentence } + if$ + format.btitle "title" output.check + note set.period.dash.check + note output + } + { duplicate$ #1 #6 substring$ "Schutz" = + { " " * format.number * output + new.sentence + "(" * format.date ")" * output + add.period$ new.sentence + format.authors.organization add.period$ output + note output + } + %% wenn irgendein anderer Typ eingetragen ist + { pop$ pop$ "" + title empty$ + { note empty$ + { url empty$ + { "there's no relevant field in " cite$ warning$ + pop$ "" + } + { format.url output }%%% + if$ + } + { note " " * output.nonnull } + if$ + } + { author empty$ + { editor empty$ + { organization empty$ + { skip$ } + { format.editors.organization output.nonnull + set.colon.after + } + if$ + } + { format.editors format.editors.organization + output.nonnull set.colon.after + } + if$ + } + { format.authors format.editors output.nonnull + set.colon.after + } + if$ + format.btitle output.nonnull + url empty$ + { format.edition output + format.howpublished output} + { format.howpublished output + format.edition.or.version output + format.url output + } + if$ + format.online.lastcheck + new.sentence + format.misc.series output + note set.period.dash.check + note output + } + if$ + } + if$ + } + if$ + } + %% wenn es keinen type gibt + { title empty$ + { note empty$ + { url empty$ + { "there's no relevant field in " cite$ warning$ + pop$ "" + } + { format.url output }%%% das waere e. reine URL + if$ + } + { note " " * output.nonnull + } + if$ + } + { author empty$ + { editor empty$ + { organization empty$ + 'skip$ + { format.editors.organization output.nonnull + set.colon.after + } + if$ + } + { format.editors format.editors.organization + output.nonnull set.colon.after + } + if$ + } + { format.authors format.editors output.nonnull + set.colon.after + } + if$ + format.btitle output.nonnull + url empty$ + { format.edition output + format.howpublished output} + { format.howpublished output + format.url output + format.edition.or.date output + format.online.lastcheck + } + if$ + new.sentence + format.misc.series output + note set.period.dash.check + note output + } + if$ + } + if$ + fin.entry +} + +FUNCTION {proceedings} +{ output.bibitem + editor empty$ + { organization empty$ + { "empty organization and editor in " cite$ * warning$ } + { organization " (Veranst.)" * output } + if$ + } + { format.editors format.editors.organization output.nonnull } + if$ + set.colon.after + format.btitle "title" output.check + volume empty$ + 'skip$ + { "{\textnormal{Bd.}}" volume tie.or.space.connect emphasize "volume" output.check } + if$ + format.address.publisher.year "publisher" output.check + new.sentence + format.series.number.din output.nonnull + pages empty$ + { skip$ } + { pages set.period.dash.check + pages format.bkpages.collat.check output + } + if$ + isbn set.period.dash.check + issn set.period.dash.check + format.isbn.issn output + note set.period.dash.check + note output + fin.entry +} + +FUNCTION {techreport} +{ output.bibitem + author empty$ + { format.editors "author and editor" output.check + format.tr.institution output.nonnull } + { format.authors format.editors output.nonnull } + if$ + set.colon.after + format.title "title" output.check + institution empty$ + 'skip$ + { author empty$ editor empty$ AND + 'skip$ + { institution new.sentence.checka + "/ " institution * output.nonnull + } + if$ + } + if$ + format.version.url output + format.online.lastcheck + format.address.publisher.year output + number new.sentence.checka + format.tr.series.or.number "number" output.check + "Forschungsbericht" format.thesis.tr.type set.period.dash.check + "Forschungsbericht" format.thesis.tr.type output + pages empty$ + { skip$ } + { pages set.period.dash.check + pages format.bkpages.collat.check output + } + if$ + isbn set.period.dash.check + issn set.period.dash.check + format.isbn.issn output + note set.period.dash.check + note "note" output.check + fin.entry +} + + +FUNCTION {unpublished} {misc}%% author, title, note muessen sein! howpublished +%% %% entfaellt natuerlich + +FUNCTION {default.type} { misc } + +MACRO {jan} {"Januar"} + +MACRO {feb} {"Februar"} + +MACRO {mar} {"M{\^^b a}rz"} + +MACRO {apr} {"April"} + +MACRO {mai} {"Mai"} + +MACRO {may} {"Mai"} + +MACRO {jun} {"Juni"} + +MACRO {jul} {"Juli"} + +MACRO {aug} {"August"} + +MACRO {sep} {"September"} + +MACRO {okt} {"Oktober"} + +MACRO {oct} {"Oktober"} + +MACRO {nov} {"November"} + +MACRO {dez} {"Dezember"} + +MACRO {dec} {"Dezember"} + +%%$$$ stillgelegte Beispiele fuer den Gebrauch von Kuerzeln (hier Zs-Titel). + +%%MACRO {acmcs} {"ACM Computing Surveys"} + +%%MACRO {acta} {"Acta Informatica"} + +READ + +FUNCTION {sortify} +{ purify$ + "l" change.case$ +} + +FUNCTION {chop.word} +{ 's := + 'len := + s #1 len substring$ = + { s len #1 + global.max$ substring$ } + 's + if$ +} + +FUNCTION {format.lab.names} +{ 's := + s #1 "{vv~}{ll}" format.name$ + s num.names$ duplicate$ + #2 > + { pop$ ua.etal * } + { #2 < + 'skip$ + { s #2 "{ff }{vv }{ll}{ jj}" format.name$ "others" = + { ua.etal * } + { und * s #2 "{vv~}{ll}" format.name$ * } + if$ + } + if$ + } + if$ +} + +FUNCTION {author.key.label} +{ author empty$ + { key empty$ + { cite$ #1 #3 substring$ } + 'key + if$ + } + { author format.lab.names } + if$ +} + +FUNCTION {author.editor.key.label} +{ author empty$ + { editor empty$ + { key empty$ + { cite$ #1 #3 substring$ } + 'key + if$ + } + { year empty$ + 'key + { editor format.lab.names } + if$ + } + if$ + } + { author format.lab.names } + if$ +} + +FUNCTION {author.key.organization.label} +{ author empty$ + { key empty$ + { organization empty$ + { cite$ #1 #3 substring$ } + { "The " #4 organization chop.word #3 text.prefix$ } + if$ + } + { key } + if$ + } + { author format.lab.names } + if$ +} + +FUNCTION {editor.key.organization.label} +{ editor empty$ + { key empty$ + { organization empty$ + { cite$ #1 #3 substring$ } + { "The " #4 organization chop.word #4 text.prefix$ } + if$ + } + { key } + if$ + } + { key empty$%% das key-feld soll zwangsweise das label machen! + { editor format.lab.names } + { key }%%das ganze label + if$ + } + if$ +} +FUNCTION {calc.short.authors} +{ type$ "book" = + type$ "booklet" = + type$ "inbook" = + or or + 'author.editor.key.label + { type$ "proceedings" = + 'editor.key.organization.label + { type$ "manual" = + 'author.key.organization.label + 'author.key.label + if$ + } + if$ + } + if$ + 'short.list := +} + +FUNCTION {calc.label} +{ calc.short.authors + short.list + "(" + * +%% year duplicate$ empty$ +%% short.list key field.or.null = or +%% { pop$ "" } +%% 'skip$ +%% if$ + year field.or.null purify$ #1 #4 substring$ + * + 'label := + short.list " " * + year field.or.null purify$ #1 #4 substring$ * 'dinat.label := +} + +FUNCTION {sort.format.names} +{ 's := + #1 'nameptr := + "" + s num.names$ 'numnames := + numnames 'namesleft := + { namesleft #0 > } + { nameptr #1 > + { " " * } + 'skip$ + if$ + s nameptr "{ll{ }}{ ff{ }}{ vv{ }}{ jj{ }}" format.name$ 't := + nameptr numnames = t "others" = and + { ua.etal * } + { t sortify * } + if$ + nameptr #1 + 'nameptr := + namesleft #1 - 'namesleft := + } + while$ +} + +FUNCTION {sort.format.title} +{ 't := + "A " #2 + "An " #3 + "Der " #4 + "Die " #4 + "Das " #4 + "Ein " #4 + "Eine " #5 + "The " #4 t chop.word + chop.word + chop.word + chop.word + chop.word + chop.word + chop.word + chop.word + sortify + #1 global.max$ substring$ +} + +FUNCTION {author.sort} +{ author empty$ + { key empty$ + { "to sort, need author or key in " cite$ * warning$ + "" + } + { key sortify } + if$ + } + { author sort.format.names } + if$ +} + +FUNCTION {author.editor.sort} +{ author empty$ + { editor empty$ + { key empty$ + { "to sort, need author, editor, or key in " cite$ * warning$ + "" + } + { key sortify } + if$ + } + { editor sort.format.names } + if$ + } + { author sort.format.names } + if$ +} + +FUNCTION {author.organization.sort} +{ author empty$ + { organization empty$ + { key empty$ + { "to sort, need author, organization, or key in " cite$ * warning$ + "" + } + { key sortify } + if$ + } + { "The " #4 organization chop.word sortify } + if$ + } + { author sort.format.names } + if$ +} + +FUNCTION {editor.organization.sort.PWD}%% so war es in plainnat +{ editor empty$ + { organization empty$ + { key empty$ + { "to sort, need editor, organization, or key in " cite$ * warning$ + "" + } + { key sortify } + if$ + } + { "The " #4 organization chop.word sortify } + if$ + } + { editor sort.format.names } + if$ +} + +FUNCTION {editor.organization.sort}%%das key-Feld soll z. Sortieren ben. werden +{ key empty$ + { editor empty$ + { organization empty$ + { "to sort, need editor, organization, or key in " cite$ * warning$ + "" + } + { "The " #4 organization chop.word sortify } + if$ + } + { editor sort.format.names } + if$ + } + { key sortify } + if$ +} + +FUNCTION {presort} +{ calc.label + label sortify + " " + * + type$ "book" = + type$ "booklet" = + type$ "inbook" = + or or + 'author.editor.sort + { type$ "proceedings" = + 'editor.organization.sort + { type$ "manual" = + 'author.organization.sort + 'author.sort + if$ + } + if$ + } + if$ + " " + * + year field.or.null sortify + * + " " + * + title field.or.null + sort.format.title + * + #1 entry.max$ substring$ + 'sort.label := + sort.label * + #1 entry.max$ substring$ + 'sort.key$ := +} + +ITERATE {presort} + +SORT + +FUNCTION {initialize.longest.label} +{ "" 'longest.label := + #0 int.to.chr$ 'last.label := + "" 'next.extra := + #0 'longest.label.width := + #0 'last.extra.num := + #0 'number.label := +} + +FUNCTION {forward.pass} +{ last.label label = + { last.extra.num #1 + 'last.extra.num := + last.extra.num int.to.chr$ 'extra.label := + } + { "a" chr.to.int$ 'last.extra.num := + "" 'extra.label := + label 'last.label := + } + if$ + number.label #1 + 'number.label := +} + +FUNCTION {reverse.pass} +{ next.extra "b" = + { "a" 'extra.label := } + 'skip$ + if$ + extra.label 'next.extra := + extra.label + duplicate$ empty$ + 'skip$ + { "{\natexlab{" swap$ * "}}" * } + if$ + 'extra.label := + label extra.label * 'label := + dinat.label extra.label * 'dinat.label := +} + +EXECUTE {initialize.longest.label} + +ITERATE {forward.pass} + +REVERSE {reverse.pass} + +FUNCTION {bib.sort.order} +{ sort.label 'sort.key$ := +} + +ITERATE {bib.sort.order} + +SORT + +FUNCTION {begin.bib} +{ preamble$ empty$ + 'skip$ + { preamble$ write$ newline$ } + if$ + "\begin{thebibliography}{" number.label int.to.str$ * "}" * + write$ newline$ newline$ + "% this bibliography is generated by nd24.bst [3.0c2] from 2005-12-21" + write$ newline$ newline$ + "\providecommand{\natexlab}[1]{#1}" + write$ newline$ + "\providecommand{\url}[1]{\texttt{#1}}" + write$ newline$ + "\makeatletter" + write$ newline$ + "\newcommand{\dinatlabel}[1]%" + write$ newline$ + "{\ifNAT@numbers\else\NAT@biblabelnum{#1}\fi}" + write$ newline$ + "\makeatother" + write$ newline$ + "\expandafter\ifx\csname urlstyle\endcsname\relax" + write$ newline$ + " \providecommand{\doi}[1]{doi: #1}\else" + write$ newline$ + " \providecommand{\doi}{doi: \begingroup \urlstyle{rm}\Url}\fi" + write$ newline$ +} + +EXECUTE {begin.bib} + +EXECUTE {init.state.consts} + +ITERATE {call.type$} + +FUNCTION {end.bib} +{ newline$ + "\end{thebibliography}" write$ newline$ +} + +EXECUTE {end.bib} diff --git a/projektdokumentation/Anhang.tex b/projektdokumentation/Anhang.tex new file mode 100644 index 0000000..2190795 --- /dev/null +++ b/projektdokumentation/Anhang.tex @@ -0,0 +1,35 @@ +% !TEX root = Projektdokumentation.tex +\section{Anhang} + +\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 + +\subsubsection{Controller-Schicht: Spring Boot REST Controller} +\label{app:ControllerSchicht} +\lstinputlisting[language=java, caption={Spring Boot REST Controller - Coinflip}]{Listings/CoinflipController.java} + +\clearpage + +\subsubsection{Service-Schicht: Business Logic} +\label{app:ServiceSchicht} +\lstinputlisting[language=java, caption={Service-Klasse mit Geschäftslogik - Coinflip}]{Listings/CoinflipService.java} + +\clearpage + +\subsubsection{Persistierung-Schicht: JPA Entity} +\label{app:PersistierungSchicht} +\lstinputlisting[language=java, caption={JPA Entity - Benutzer}]{Listings/UserEntity.java} + +\clearpage + +\subsubsection{Konfiguration: Application Properties} +\label{app:Konfiguration} +\lstinputlisting[caption={Spring Boot Anwendungskonfiguration}]{Listings/application.properties} + +\clearpage diff --git a/projektdokumentation/Anhang/AnhangBenutzerDoku.tex b/projektdokumentation/Anhang/AnhangBenutzerDoku.tex new file mode 100644 index 0000000..f6e2892 --- /dev/null +++ b/projektdokumentation/Anhang/AnhangBenutzerDoku.tex @@ -0,0 +1,15 @@ +\subsection{Benutzerdokumentation} +\label{app:BenutzerDoku} +Ausschnitt aus der Benutzerdokumentation: + +\begin{table}[htb] +\begin{tabularx}{\textwidth}{cXX} +\rowcolor{heading}\textbf{Symbol} & \textbf{Bedeutung global} & \textbf{Bedeutung einzeln} \\ +\includegraphicstotab[]{weather-clear.png} & Alle Module weisen den gleichen Stand auf. & Das Modul ist auf dem gleichen Stand wie das Modul auf der vorherigen Umgebung. \\ +\rowcolor{odd}\includegraphicstotab[]{weather-clear-night.png} & Es existieren keine Module (fachlich nicht möglich). & Weder auf der aktuellen noch auf der vorherigen Umgebung sind Module angelegt. Es kann also auch nichts übertragen werden. \\ +\includegraphicstotab[]{weather-few-clouds-night.png} & Ein Modul muss durch das Übertragen von der vorherigen Umgebung erstellt werden. & Das Modul der vorherigen Umgebung kann übertragen werden, auf dieser Umgebung ist noch kein Modul vorhanden. \\ +\rowcolor{odd}\includegraphicstotab[]{weather-few-clouds.png} & Auf einer vorherigen Umgebung gibt es ein Modul, welches übertragen werden kann, um das nächste zu aktualisieren. & Das Modul der vorherigen Umgebung kann übertragen werden um dieses zu aktualisieren. \\ +\includegraphicstotab[]{weather-storm.png} & Ein Modul auf einer Umgebung wurde entgegen des Entwicklungsprozesses gespeichert. & Das aktuelle Modul ist neuer als das Modul auf der vorherigen Umgebung oder die vorherige Umgebung wurde übersprungen. \\ +\end{tabularx} +\end{table} + diff --git a/projektdokumentation/Anhang/AnhangDoc.tex b/projektdokumentation/Anhang/AnhangDoc.tex new file mode 100644 index 0000000..e0a46ba --- /dev/null +++ b/projektdokumentation/Anhang/AnhangDoc.tex @@ -0,0 +1,7 @@ +\subsection{Entwicklerdokumentation} +\label{app:Doc} +\begin{center} +\includegraphics[page=1, width=0.9\textwidth]{doc.pdf} + +\includegraphics[page=2, width=0.9\textwidth]{doc.pdf} +\end{center} diff --git a/projektdokumentation/Anhang/AnhangEntwuerfe.tex b/projektdokumentation/Anhang/AnhangEntwuerfe.tex new file mode 100644 index 0000000..d9b26df --- /dev/null +++ b/projektdokumentation/Anhang/AnhangEntwuerfe.tex @@ -0,0 +1,19 @@ +\subsection{Oberflächenentwürfe} +\label{app:Entwuerfe} +\begin{figure}[htb] +\centering +\includegraphicsKeepAspectRatio{MockupModules.pdf}{0.7} +\caption{Liste der Module mit Filtermöglichkeiten} +\end{figure} + +\begin{figure}[htb] +\centering +\includegraphicsKeepAspectRatio{MockupModul.pdf}{0.7} +\caption{Anzeige der Übersichtsseite einzelner Module} +\end{figure} + +\begin{figure}[htb] +\centering +\includegraphicsKeepAspectRatio{MockupTag.pdf}{0.7} +\caption{Anzeige und Filterung der Module nach Tags} +\end{figure} diff --git a/projektdokumentation/Anhang/AnhangLastenheft.tex b/projektdokumentation/Anhang/AnhangLastenheft.tex new file mode 100644 index 0000000..2452331 --- /dev/null +++ b/projektdokumentation/Anhang/AnhangLastenheft.tex @@ -0,0 +1,22 @@ +\subsection{Lastenheft (Auszug)} +\label{app:Lastenheft} +Im folgenden Auszug des Lastenheftes werden die Anforderungen definiert, die an die neu entwickelte Anwendung gestellt werden. + +Die Anwendung muss folgende Anforderungen erfüllen: +\begin{enumerate}[itemsep=0em,partopsep=0em,parsep=0em,topsep=0em] +\item Verarbeitung der empfangenen Daten von \ac{IX} + \begin{enumerate} + \item Die Anwendung muss Produkt- und Nutzerdaten von der \ac{IX}-\ac{API} abrufen können. + \item Die geholten Daten sollen temporär zwischengespeichert werden, um den Import zu \ac{M2} von der Datenbeschaffung abzukapseln + \item Die gespeicherten Daten werden getrennt von der \ac{IX}-\ac{API} an die \ac{M2} Import-Funktionen gegeben und so in das \ac{M2} System integriert + \end{enumerate} +\item Sonstige Anforderungen + \begin{enumerate} + \item Die Anwendung muss ohne das Installieren einer zusätzlichen Software über das Terminal erreichbar sein. + \item Die Import der Daten muss jede Nacht \bzw nach jedem Fehlschlag automatisch aktualisiert werden. + \item Die Anwendung soll jederzeit erreichbar sein. + \item Da sich die Entwickler auf die Anwendung verlassen, muss diese korrekte Daten liefern und darf keinen Interpretationsspielraum lassen. + \item Die Anwendung muss so flexibel sein, dass sie bei Änderungen im Entwicklungsprozess einfach angepasst werden kann. + \end{enumerate} +\end{enumerate} + diff --git a/projektdokumentation/Anhang/AnhangPflichtenheft.tex b/projektdokumentation/Anhang/AnhangPflichtenheft.tex new file mode 100644 index 0000000..2875f3e --- /dev/null +++ b/projektdokumentation/Anhang/AnhangPflichtenheft.tex @@ -0,0 +1,52 @@ +\subsection{Pflichtenheft (Auszug)} +\label{app:Pflichtenheft} + +\subsubsection*{Zielbestimmung} + +\begin{enumerate}[itemsep=0em,partopsep=0em,parsep=0em,topsep=0em] +\item Musskriterien % Wikipedia: für das Produkt unabdingbare Leistungen, die in jedem Fall erfüllt werden müssen + \begin{enumerate} + \item Modul-Liste: Zeigt eine filterbare Liste der Module mit den dazugehörigen Kerninformationen sowie Symbolen zur Einhaltung des Entwicklungsprozesses an + \begin{itemize} + \item In der Liste wird der Name, die Bibliothek und Daten zum Source und Kompilat eines Moduls angezeigt. + \item Ebenfalls wird der Status des Moduls hinsichtlich Source und Kompilat angezeigt. Dazu gibt es unterschiedliche Status-Zeichen, welche symbolisieren in wie weit der Entwicklungsprozess eingehalten wurde \bzw welche Schritte als nächstes getan werden müssen. So gibt es \zB Zeichen für das Einhalten oder Verletzen des Prozesses oder den Hinweis auf den nächsten zu tätigenden Schritt. + \item Weiterhin werden die Benutzer und Zeitpunkte der aktuellen Version der Sourcen und Kompilate angezeigt. Dazu kann vorher ausgewählt werden, von welcher Umgebung diese Daten gelesen werden sollen. + \item Es kann eine Filterung nach allen angezeigten Daten vorgenommen werden. Die Daten zu den Sourcen sind historisiert. Durch die Filterung ist es möglich, auch Module zu finden, die in der Zwischenzeit schon von einem anderen Benutzer editiert wurden. + \end{itemize} + \item Tag-Liste: Bietet die Möglichkeit die Module anhand von Tags zu filtern. + \begin{itemize} + \item Es sollen die Tags angezeigt werden, nach denen bereits gefiltert wird und die, die noch der Filterung hinzugefügt werden könnten, ohne dass die Ergebnisliste leer wird. + \item Zusätzlich sollen die Module angezeigt werden, die den Filterkriterien entsprechen. Sollten die Filterkriterien leer sein, werden nur die Module angezeigt, welche mit einem Tag versehen sind. + \end{itemize} + \item Import der Moduldaten aus einer bereitgestellten \acs{CSV}-Datei + \begin{itemize} + \item Es wird täglich eine Datei mit den Daten der aktuellen Module erstellt. Diese Datei wird (durch einen Cronjob) automatisch nachts importiert. + \item Dabei wird für jedes importierte Modul ein Zeitstempel aktualisiert, damit festgestellt werden kann, wenn ein Modul gelöscht wurde. + \item Die Datei enthält die Namen der Umgebung, der Bibliothek und des Moduls, den Programmtyp, den Benutzer und Zeitpunkt des Sourcecodes sowie des Kompilats und den Hash des Sourcecodes. + \item Sollte sich ein Modul verändert haben, werden die entsprechenden Daten in der Datenbank aktualisiert. Die Veränderungen am Source werden dabei aber nicht ersetzt, sondern historisiert. + \end{itemize} + \item Import der Informationen aus \ac{SVN}. Durch einen \gqq{post-commit-hook} wird nach jedem Einchecken eines Moduls ein \acs{PHP}-Script auf der Konsole aufgerufen, welches die Informationen, die vom \ac{SVN}-Kommandozeilentool geliefert werden, an \acs{NatInfo} übergibt. + \item Parsen der Sourcen + \begin{itemize} + \item Die Sourcen der Entwicklungsumgebung werden nach Tags, Links zu Artikeln im Wiki und Programmbeschreibungen durchsucht. + \item Diese Daten werden dann entsprechend angelegt, aktualisiert oder nicht mehr gesetzte Tags/Wikiartikel entfernt. + \end{itemize} + \item Sonstiges + \begin{itemize} + \item Das Programm läuft als Webanwendung im Intranet. + \item Die Anwendung soll möglichst leicht erweiterbar sein und auch von anderen Entwicklungsprozessen ausgehen können. + \item Eine Konfiguration soll möglichst in zentralen Konfigurationsdateien erfolgen. + \end{itemize} + \end{enumerate} +\end{enumerate} + +\subsubsection*{Produkteinsatz} + +\begin{enumerate}[itemsep=0em,partopsep=0em,parsep=0em,topsep=0em] +\item{Anwendungsbereiche\\ +Die Webanwendung dient als Anlaufstelle für die Entwicklung. Dort sind alle Informationen für die Module an einer Stelle gesammelt. Vorher getrennte Anwendungen werden ersetzt \bzw verlinkt.} +\item{Zielgruppen\\ +\NI wird lediglich von den \ac{Natural}-Entwicklern in der EDV-Abteilung genutzt.} +\item{Betriebsbedingungen\\ % Wikipedia: physikalische Umgebung des Systems, tägliche Betriebszeit, ständige Beobachtung des Systems durch Bediener oder unbeaufsichtigter Betrieb +Die nötigen Betriebsbedingungen, also der Webserver, die Datenbank, die Versionsverwaltung, das Wiki und der nächtliche Export sind bereits vorhanden und konfiguriert. Durch einen täglichen Cronjob werden entsprechende Daten aktualisiert, die Webanwendung ist jederzeit aus dem Intranet heraus erreichbar.} +\end{enumerate} diff --git a/projektdokumentation/Anhang/AnhangRessourcen.tex b/projektdokumentation/Anhang/AnhangRessourcen.tex new file mode 100644 index 0000000..12cb409 --- /dev/null +++ b/projektdokumentation/Anhang/AnhangRessourcen.tex @@ -0,0 +1,28 @@ +\subsection{Verwendete Ressourcen} +\label{app:Ressourcen} + Hardware +\begin{itemize} + \item Büroarbeitsplatz (Schreibtisch, ergonomischer Stuhl) + \item Fujitsu Lifebook U-Series - Notebook + \item Peripheriegeräte für Notebook (Tastatur, Maus) +\end{itemize} + +Software +\begin{itemize} + \item Debian Derivat Ubuntu 18.04 - Betriebssystem + \item JetBrains PHPStorm - Entwicklungsumgebung \ac{PHP} + \item git - Verteilte Versionsverwaltung + \item Docker - Open Source Management von virtuellen Maschinen (Containerbasiert) + \item mySQL - Open Source relationelles Datenbank Management System + \item Composer - Open Source Package Management System auf Anwendungsebene für \ac{PHP} + \item Magento 2 Community Edition - Open Source \ac{eCommerce} Platform + \item TeXify IDEA - Open Source Plugin für PHPStorm als Distribution des Textsatzsystems TeX + \item PHPUnit - Framework zur Durchführung von Unit-Tests + \item LaTeX-Vorlage zur IHK-Projektdokumentation für Fachinformatiker Anwendungsentwicklung von Stefan Macke - \url{http://fiae.link/LaTeXVorlageFIAE} +\end{itemize} + +Personal +\begin{itemize} + \item Mitarbeiter Softwareentwicklung der \ac{NSD} - Festlegung der Anforderungen, Abnahme des Projektes \& Review des Codes + \item Auszubildender Fachinformatiker als Entwickler - Umsetzung des Projektes +\end{itemize} diff --git a/projektdokumentation/Anhang/AnhangScreenshots.tex b/projektdokumentation/Anhang/AnhangScreenshots.tex new file mode 100644 index 0000000..7c5ddcb --- /dev/null +++ b/projektdokumentation/Anhang/AnhangScreenshots.tex @@ -0,0 +1,14 @@ +\subsection{Screenshots der Anwendung} +\label{Screenshots} +\begin{figure}[htb] +\centering +\includegraphicsKeepAspectRatio{tagliste.pdf}{1} +\caption{Anzeige und Filterung der Module nach Tags} +\end{figure} +\clearpage +\begin{figure}[htb] +\centering +\includegraphicsKeepAspectRatio{modulliste.pdf}{1} +\caption{Liste der Module mit Filtermöglichkeiten} +\end{figure} +\clearpage diff --git a/projektdokumentation/Anhang/AnhangTest.tex b/projektdokumentation/Anhang/AnhangTest.tex new file mode 100644 index 0000000..ffea696 --- /dev/null +++ b/projektdokumentation/Anhang/AnhangTest.tex @@ -0,0 +1,9 @@ +\subsection{Testfall und sein Aufruf auf der Konsole} +\label{app:Test} +\lstinputlisting[language=php, caption={Testfall in PHP}]{Listings/tests.php} +\clearpage +\begin{figure}[htb] +\centering +\includegraphicsKeepAspectRatio{testcase.jpg}{1} +\caption{Aufruf des Testfalls auf der Konsole} +\end{figure} diff --git a/projektdokumentation/Befehle.tex b/projektdokumentation/Befehle.tex new file mode 100644 index 0000000..701af75 --- /dev/null +++ b/projektdokumentation/Befehle.tex @@ -0,0 +1,4 @@ +% Abkürzungen +\newcommand{\Versis}{\textsc{Versis}\xspace} +\newcommand{\NI}{NatInfo\xspace} +\newcommand{\AO}{\textsc{Alte Oldenburger} Krankenversicherung\xspace} diff --git a/projektdokumentation/Bibliographie.bib b/projektdokumentation/Bibliographie.bib new file mode 100644 index 0000000..6dd2db6 --- /dev/null +++ b/projektdokumentation/Bibliographie.bib @@ -0,0 +1,92 @@ +% This file was created with JabRef 2.4.2. +% Encoding: UTF8 + +@book{Rohrer2011, + address = {Solingen}, + edition = {5.}, + title = {Clevere Tipps für die Projektarbeit - {IT-Berufe:} Abschlussprüfung Teil A}, + isbn = {3882347538}, + url = {http://fiae.link/ClevereTippsFuerDieProjektarbeit}, + publisher = {{U-Form-Verlag}}, + author = {Rohrer, Anselm and Sedlacek, Ramona}, + year = {2011} +} + +@misc{Bundesgesetzblatt48, + title = {Verordnung über die Berufsausbildung im Bereich der Informations- und Telekommunikationstechnik}, + url = {http://fiae.link/VerordnungITBerufe}, + author = {{Regierung der Bundesrepublik Deutschland}}, + month = jul, + year = {1997}, + pages = {1741--1799} +} + +@techreport{BMBF2000, + address = {Bonn}, + type = {Abschlussbericht}, + title = {Umsetzungshilfen für die neue Prüfungsstruktur der {IT-Berufe}}, + shorttitle = {{BMBF2000}}, + url = {http://fiae.link/UmsetzungshilfenITBerufe}, + author = {{Bundesministerium für Bildung und Forschung}}, + institution = {{Bundesministerium für Bildung und Forschung}}, + month = jul, + year = {2000}, + pages = {476} +} + +@techreport{Grashorn2010, + address = {Vechta}, + type = {Dokumentation zur Projektarbeit}, + title = {Entwicklung von NatInfo -- Webbasiertes Tool zur Unterstützung der Entwickler}, + author = {Dirk Grashorn}, + institution = {{Alte Oldenburger Krankenversicherung AG}}, + month = apr, + year = {2010}, +} + +@misc{MerkblattIHK, + address = {Oldenburg}, + title = {Merkblatt zur Abschlussprüfung der IT-Berufe}, + author = {{IHK Oldenburg}}, + institution = {{Oldenburgische Industrie- und Handelskammer}}, + month = may, + year = {2006}, + url = {http://fiae.link/MerkblattDokuOldenburg}, +} + +@misc{BewertungsmatrikIHK, + address = {Darmstadt}, + title = {Bewertungsmatrix für Fachinformatiker/innen Anwendungsentwicklung}, + author = {{IHK Darmstadt}}, + institution = {{IHK Darmstadt Rhein Main Neckar}}, + month = mar, + year = {2011}, + url = {http://fiae.link/BewertungsmatrixDokuDarmstadt}, +} + +@misc{ISO9126, + title = {{Software-Engineering} – Qualität von {Software-Produkten} – Teil 1: Qualitätsmodell}, + author = {{ISO/IEC 9126-1}}, + month = jun, + year = {2001} +} + +@BOOKLET{phpDoc, + title = {{phpDocumentor-Website}}, + author = {{phpdoc.org}}, + lastchecked = {20.04.2010}, + year = {2010}, + owner = {grashorn}, + timestamp = {2010.04.22}, + url = {http://www.phpdoc.org/} +} + +@BOOKLET{Symfony, + title = {Symfony - Open-Source PHP Web Framework}, + author = {{Sensio Labs}}, + lastchecked = {20.04.2010}, + year = {2010}, + owner = {grashorn}, + timestamp = {2010.04.22}, + url = {http://www.symfony-project.org/} +} diff --git a/projektdokumentation/Bilder/LogoBetrieb.pdf b/projektdokumentation/Bilder/LogoBetrieb.pdf new file mode 100644 index 0000000..8f58d01 Binary files /dev/null and b/projektdokumentation/Bilder/LogoBetrieb.pdf differ diff --git a/projektdokumentation/Bilder/component-diagram.drawio.png b/projektdokumentation/Bilder/component-diagram.drawio.png new file mode 100644 index 0000000..519fd7f Binary files /dev/null and b/projektdokumentation/Bilder/component-diagram.drawio.png differ diff --git a/projektdokumentation/Bilder/container-diagram.drawio.png b/projektdokumentation/Bilder/container-diagram.drawio.png new file mode 100644 index 0000000..c3013e7 Binary files /dev/null and b/projektdokumentation/Bilder/container-diagram.drawio.png differ diff --git a/projektdokumentation/Bilder/hitec-logo.png b/projektdokumentation/Bilder/hitec-logo.png new file mode 100644 index 0000000..93722d9 Binary files /dev/null and b/projektdokumentation/Bilder/hitec-logo.png differ diff --git a/projektdokumentation/Bilder/login.png b/projektdokumentation/Bilder/login.png new file mode 100755 index 0000000..ee8bcf8 Binary files /dev/null and b/projektdokumentation/Bilder/login.png differ diff --git a/projektdokumentation/Bilder/oauth.png b/projektdokumentation/Bilder/oauth.png new file mode 100755 index 0000000..a145569 Binary files /dev/null and b/projektdokumentation/Bilder/oauth.png differ diff --git a/projektdokumentation/Deckblatt.tex b/projektdokumentation/Deckblatt.tex new file mode 100644 index 0000000..51f3840 --- /dev/null +++ b/projektdokumentation/Deckblatt.tex @@ -0,0 +1,27 @@ +% !TEX root = Projektdokumentation.tex +\begin{titlepage} + + \begin{center} + \includegraphics[scale=0.25]{hitec-logo.png}\\[1ex] + + \Large{\ausbildungsberuf}\\ + \LARGE{\betreff}\\[4ex] + + \huge{\textbf{\titel}}\\[1.5ex] + \Large{\textbf{\untertitel}}\\[4ex] + + \normalsize + Projektzeitraum: \projektZeitraum\\[3em] + \textbf{Projektteilnehmer:}\\ + Constantin Simonis\\ + Phan Huy Tran\\ + Jan-Marlon Leibl\\ + Jan Klattenhoff\\ + Lea Ziemke\\ + + \textbf{Projektaufsicht:}\\ + Katrin Deeken\\ + Bernd Heidemann\\ + \end{center} + +\end{titlepage} diff --git a/projektdokumentation/Inhalt.tex b/projektdokumentation/Inhalt.tex new file mode 100644 index 0000000..1de4c1b --- /dev/null +++ b/projektdokumentation/Inhalt.tex @@ -0,0 +1,16 @@ +% !TEX root = Projektdokumentation.tex +\input{Inhalt/Einleitung} +\input{Inhalt/Vorbereitung.tex} +\input{Inhalt/Projektarchitektur} +\input{Inhalt/CI} +\input{Inhalt/Auth.tex} +\input{Inhalt/Dice.tex} +\input{Inhalt/Slots.tex} +\input{Inhalt/Coinflip.tex} +\input{Inhalt/Blackjack.tex} +\input{Inhalt/Lootboxes.tex} + +\input{Inhalt/Deployment.tex} + +\input{Inhalt/Wirtschaftlichebetrachtung.tex} +\input{Inhalt/Abschluss.tex} diff --git a/projektdokumentation/Inhalt/Abschluss.tex b/projektdokumentation/Inhalt/Abschluss.tex new file mode 100644 index 0000000..2fcebe3 --- /dev/null +++ b/projektdokumentation/Inhalt/Abschluss.tex @@ -0,0 +1,12 @@ +% !TEX root = ../Projektdokumentation.tex +\section{Abschluss} + +\subsection{Projektziel} +Im Rahmen der Soll-Analyse konnten wesentliche Bestandteile des Projekts erfolgreich umgesetzt werden. Aufgrund zeitlicher Einschränkungen sowie unvorhergesehener technischer Herausforderungen war es jedoch nicht möglich, sämtliche ursprünglich geplanten Funktionen vollständig zu realisieren. Während der Entwicklungsphase wurde ein besonderer Schwerpunkt auf die technischen Grundlagen gelegt, insbesondere auf die sichere Authentifizierung, die serverseitige Logik sowie die Bereitstellung einer stabilen Infrastruktur. Monetarisierungsstrategien und benutzerorientierte Aspekte wurden in dieser Phase hingegen nur am Rande berücksichtigt. + +Im Verlauf der Ausarbeitung wurde zudem deutlich, dass die Umsetzung einzelner Spiele deutlich komplexer ist als zunächst angenommen. Aus diesem Grund wurde die Entwicklung auf den Kern des Spielkonzepts reduziert, wohingegen begleitende Elemente wie soziale Funktionen oder ein vollständig responsives Design vorerst nicht weiter ausgearbeitet wurden. Im weiteren Projektverlauf wurde deutlich, dass die Implementierung von Poker und Plinko nicht mehr im geplanten Zeitrahmen realisiert werden konnte. Insbesondere die Entwicklung der Authentifizierungsmechanismen sowie das Deployment der Anwendung erforderten einen höheren Aufwand als erwartet. + +\subsection{Fazit} +Die Zusammenarbeit im Team kann als grundsätzlich positiv bewertet werden. Es sei besonders hervorgehoben, dass eine offene und funktionierende Kommunikation existierte, die es ermöglichte, Herausforderungen gemeinsam zu bewältigen und Entscheidungen transparent zu treffen. Gleichzeitig wurde jedoch Optimierungspotenzial in der Organisation und Strukturierung der Arbeitsprozesse aufgezeigt. Das Projektverhalten zeigte phasenweise Unkoordiniertheit und chaotische Züge, was die Effizienz beeinträchtigte. Das Ticket-Handling hätte klarer und konsequenter erfolgen müssen, um Aufgaben besser zu priorisieren und den Überblick über den Projektfortschritt zu wahren. Trotz der zuvor genannten strukturellen Schwächen war eine gute inhaltliche Zusammenarbeit innerhalb des Teams zu verzeichnen, in deren Folge bedeutende Meilensteine erreicht werden konnten. + +Trotzdem war das Projekt von erheblicher Frustration geprägt, deren Hauptursache die als unzureichend empfundene Technologie, insbesondere Java, war. Gepaart mit Mängeln im Projektmanagement, wie zu spät begonnener Dokumentation und ineffektiven Retrospektiven, führte der massive Zeitdruck zu einem unbefriedigenden Ergebnis. Das Gesamterlebnis gipfelt in dem Gefühl, letztlich „garnichts“ erreicht zu haben, und dem nachdrücklichen Wunsch, Java zukünftig zu meiden diff --git a/projektdokumentation/Inhalt/Auth.tex b/projektdokumentation/Inhalt/Auth.tex new file mode 100644 index 0000000..aebbbeb --- /dev/null +++ b/projektdokumentation/Inhalt/Auth.tex @@ -0,0 +1,23 @@ +\section{Authentifizierung} +\label{sec:Authentifizierung} +Die Authentifizierung gegenüber der \acs{API} erfolgt über einen \acs{JWT}-Token, der dem Frontend nach Erfolgreicher Authentifizierung übergeben wird. +Authentifizierung läuft über zwei verschiedene Wege ab: +\subsection{Registrierung mit username/password} Der Nutzer füllt ein Registrierungs-Formular aus, welches die Anmeldedaten an die \acs{API} sendet. Diese validitert die Anmeldedaten und legt bei Erfolg einen neuen Nutzer an. Anschließend wird eine E-Mail-Verifizierungs-Mail gesendet. Bis der Link in der Verifizierungs-Mail nicht angeklickt wurde, ist der Nutzer nicht aktiv und kann sich nicht anmelden. Nach dem Klick auf den Link wird der Nutzer aktiviert und kann sich anmelden. + +\subsection{Login mit username/password} Der Nutzer füllt ein Anmelde-Formular, welches die Anmeldedaten an die \acs{API} sendet. Diese prüft die Anmeldedaten und gibt bei Erfolg einen \acs{JWT}-Token zurück. Falls kein Nutzer mit den Anmeldedaten existiert, wird der Nutzer aufgefordert einen Account zu erstellen. + +\begin{figure} + \centering + \includegraphics[width=0.3\textwidth]{login.png} + \caption{Login-Formular der Anwendung} + \label{fig:login} +\end{figure} + +\subsection{Login über Oauth (Open Authorization)} Der Nutzer meldet sich mit einem Oauth-Provider an, in unserem Fall Google oder Github. Das Backend leitet den Nutzer zum Oauth-Provider weiter, der die Anmeldedaten prüft und bei Erfolg den Nutzer auf die Applikation weiterleitet und einen Authorization-Code zurück gibt. Mit diesem Code holt sich die \acs{API} einen \acs{JWT} vom jeweiligen Provider und holt sich Nutzer-Informationen. Mit diesen wird dann ein existierender Nutzer eingeloggt, oder registriert falls der Nutzer noch kein Konto hatte. Anschließend wird von der \acs{API} ein \acs{JWT} generiert und an das Frontend weitergegeben. + +\begin{figure} + \centering + \includegraphics[width=0.45\textwidth]{oauth.png} + \caption{OAuth-Authentifizierungsablauf} + \label{fig:oauth} +\end{figure} \ No newline at end of file diff --git a/projektdokumentation/Inhalt/Blackjack.tex b/projektdokumentation/Inhalt/Blackjack.tex new file mode 100644 index 0000000..b42aeff --- /dev/null +++ b/projektdokumentation/Inhalt/Blackjack.tex @@ -0,0 +1,52 @@ +\section{Blackjack} + +\subsection{Was ist Blackjack?} +Blackjack ist eines der populärsten Kartenspiele in Casinos weltweit und wurde als vollständiges Spiel in die Casino-Plattform integriert. Das Ziel des Spiels ist es, mit den eigenen Karten einen Wert von 21 zu erreichen oder näher an 21 heranzukommen als der Dealer, ohne dabei über 21 zu gehen (Bust). Das Spiel kombiniert Strategie und Glück, da Spieler Entscheidungen über ihre Spielzüge treffen müssen, während sie gegen einen automatisierten Dealer antreten. + +Die Implementierung folgt den klassischen Blackjack-Regeln: Zahlenkarten haben ihren Nennwert, Bildkarten (König, Dame, Bube) zählen 10 Punkte, und Asse können je nach Situation als 1 oder 11 gewertet werden. Ein "Blackjack" (21 mit den ersten beiden Karten) zahlt mit einem Bonus von 1,5x des Einsatzes aus, während reguläre Gewinne den doppelten Einsatz zurückgeben. + +\subsubsection{Kartensystem und Deck-Verwaltung} +Das Blackjack-System verwendet ein vollständiges 52-Karten-Deck mit vier Farben (Herz, Karo, Kreuz, Pik) und 13 Rängen (2-10, Bube, Dame, König, Ass). Die Karten werden in der Datenbank als separate Entitäten gespeichert, wobei jede Karte einem Typ zugeordnet ist: DECK (noch im Stapel), PLAYER (Spielerhand) oder DEALER (Dealerhand). + +Die DeckService-Klasse verwaltet die Kartenerstellung und -verteilung. Bei Spielbeginn wird ein neues, gemischtes Deck erstellt und die Karten werden entsprechend den Spielregeln verteilt: Der Spieler erhält zwei Karten, der Dealer erhält eine Karte. Die Zufallsmischung erfolgt über java.util.Random, um eine faire Kartenverteilung zu gewährleisten. + +\subsubsection{Spielzustände und Ablauf} +Das Spiel durchläuft verschiedene definierte Zustände: +\begin{itemize} + \item \textbf{IN\_PROGRESS:} Das Spiel läuft aktiv, der Spieler kann Aktionen ausführen + \item \textbf{PLAYER\_BLACKJACK:} Der Spieler hat einen natürlichen Blackjack (21 mit zwei Karten) + \item \textbf{PLAYER\_WON:} Der Spieler hat gewonnen ohne Blackjack + \item \textbf{PLAYER\_LOST:} Der Spieler hat verloren oder sich überkauft + \item \textbf{DRAW:} Unentschieden zwischen Spieler und Dealer +\end{itemize} + +Der Spielablauf gestaltet sich folgendermaßen: +\begin{enumerate} + \item Spieler startet das Spiel mit einem Einsatz + \item Austeilung der Anfangskarten (2 für Spieler, 1 für Dealer) + \item Spieler trifft Entscheidungen: Hit (weitere Karte), Stand (keine weitere Karte), Double Down (Einsatz verdoppeln und eine Karte) + \item Bei Stand aktiviert sich die Dealer-Logik: Dealer zieht Karten bis mindestens 17 erreicht sind + \item Gewinnermittlung und Auszahlung entsprechend des Ergebnisses +\end{enumerate} + +\subsubsection{Handwertberechnung} +Die Berechnung des Handwerts stellt den Kern der Spiellogik dar. Zunächst werden die Grundwerte aller Karten addiert, wobei Asse initial mit 11 bewertet werden. Falls die Summe 21 übersteigt und Asse vorhanden sind, werden diese nacheinander von 11 auf 1 reduziert, bis entweder die Summe unter 22 liegt oder keine Asse mehr als 11 gewertet werden können. + +Diese flexible Ass-Bewertung ermöglicht optimale Strategien: Ein Ass kann als "Soft" (11) oder "Hard" (1) gewertet werden, abhängig von den anderen Karten in der Hand. Die Implementierung sorgt automatisch für die bestmögliche Bewertung der Spielerhand. + +\subsubsection{Wettsystem und Auszahlungen} +Das Blackjack-Spiel ist vollständig in das Wettsystem der Plattform integriert. Der Einsatz wird zu Spielbeginn vom Spielerkonto abgebucht und bei Gewinn entsprechend der Auszahlungsregeln gutgeschrieben: + +\begin{itemize} + \item \textbf{Blackjack:} 1,5x Einsatz plus ursprünglicher Einsatz zurück + \item \textbf{Regulärer Gewinn:} 2x Einsatz (Verdopplung) + \item \textbf{Unentschieden:} Ursprünglicher Einsatz wird zurückerstattet + \item \textbf{Niederlage:} Kein Gewinn, Einsatz verloren +\end{itemize} + +Die Double-Down-Funktion verdoppelt den ursprünglichen Einsatz und gibt dem Spieler genau eine weitere Karte, danach ist das Spiel automatisch beendet. Diese Aktion ist nur mit den ersten beiden Karten möglich und erfordert ausreichendes Guthaben für den zusätzlichen Einsatz. + +\subsubsection{Frontend-Integration} +Das Frontend bietet eine vollständig animierte Blackjack-Erfahrung mit visueller Kartenrepräsentation und intuitiver Benutzeroberfläche. Die Spielerkarten werden offen dargestellt, während die zweite Dealer-Karte bis zum Spielende verdeckt bleibt. Sound-Effekte und Animationen verstärken die Spielerfahrung. + +Die Zustandssynchronisation zwischen Frontend und Backend erfolgt über reactive Programmierung mit Angular Signals, wodurch Änderungen in Echtzeit dargestellt werden. Fehlerbehandlung sorgt für robuste Spielzustände auch bei Netzwerkproblemen, und die Benutzeroberfläche passt sich dynamisch an verschiedene Spielsituationen an. \ No newline at end of file diff --git a/projektdokumentation/Inhalt/CI.tex b/projektdokumentation/Inhalt/CI.tex new file mode 100644 index 0000000..340124d --- /dev/null +++ b/projektdokumentation/Inhalt/CI.tex @@ -0,0 +1,53 @@ +% !TEX root = ../Projektdokumentation.tex +\section{Continuous Integration} +\label{sec:CI} + +Das Projekt verwendet Gitea Actions\footnote{Gitea Actions - \url{https://docs.gitea.com/usage/actions/overview}} als \ac{CI/CD}-Pipeline, welche vollständig kompatibel mit GitHub Actions ist. +Entsprechend den Qualitätsanforderungen soll eine hohe Code-Qualität durch automatisierte Tests gewährleistet werden. + +\subsection{Aufbau der CI-Pipeline} +\label{sec:ci-pipeline} + +Die Haupt-\ac{CI}-Pipeline wird durch die Datei \Datei{ci.yml} definiert und bei Pull Requests ausgelöst. Aufgrund der separaten Frontend- und Backend-Komponenten +wurde eine \Fachbegriff{Change Detection} implementiert, welche nur relevante Tests für geänderte Bereiche ausführt. + +Ein initialer Job identifiziert geänderte Dateien und ermöglicht eine selektive Ausführung: +\begin{itemize} + \item Backend-Änderungen: \Datei{backend/**} + \item Frontend-Änderungen: \Datei{frontend/**} + \item Workflow-Änderungen: \Datei{.gitea/workflows/**} +\end{itemize} + +\subsubsection{Backend-Qualitätssicherung} +Für Backend-Änderungen werden folgende Prüfungen durchgeführt: +\begin{itemize} + \item \textbf{Unit Tests:} Ausführung mit \Eingabe{./gradlew test} in OpenJDK 23 Container + \item \textbf{Checkstyle:} Code-Style-Validierung mit Caching-Mechanismus + \item \textbf{Docker Build:} Überprüfung der Build-Funktionalität +\end{itemize} + +\subsubsection{Frontend-Qualitätssicherung} +Für Frontend-Änderungen wird eine umfassende Testsuite ausgeführt: +\begin{itemize} + \item \textbf{ESLint:} Code-Qualitätsprüfung mit \Eingabe{bun run lint} + \item \textbf{Prettier:} Code-Formatierungsvalidierung + \item \textbf{Build-Test:} Produktions-Build-Validierung mit \Eingabe{bun run build} + \item \textbf{Playwright \ac{E2E} Tests:} End-to-End-Tests mit automatischem Backend-Start + \item \textbf{Docker Build:} Validierung der Container-Erstellung +\end{itemize} + +\subsection{Release-Management} +\label{sec:release-pipeline} + +Das Release-Management erfolgt automatisiert durch die \Datei{release.yml} Pipeline bei Pushes auf den \texttt{main}-Branch. +Die Implementierung folgt \Fachbegriff{Semantic Versioning}\footnote{Semantic Versioning - \url{https://semver.org/}} und \Fachbegriff{Conventional Commits}\footnote{Conventional Commits - \url{https://www.conventionalcommits.org/}}. + +Die Release-Pipeline umfasst: +\begin{enumerate} + \item \textbf{Semantic Release:} Automatische Versionierung basierend auf Commit-Nachrichten + \item \textbf{Docker Image Build:} Parallele Erstellung von Backend- und Frontend-Images + \item \textbf{Registry Push:} Upload zur privaten Gitea Docker Registry +\end{enumerate} + +Die \ac{CI/CD}-Pipeline implementiert Performance-Optimierungen wie intelligentes Caching, Concurrency Control und selektive Job-Ausführung. +Diese Automatisierung gewährleistet eine hohe Software-Qualität bei effizienten Entwicklungsprozessen. diff --git a/projektdokumentation/Inhalt/Coinflip.tex b/projektdokumentation/Inhalt/Coinflip.tex new file mode 100644 index 0000000..1e54bdf --- /dev/null +++ b/projektdokumentation/Inhalt/Coinflip.tex @@ -0,0 +1,41 @@ +\section{Coinflip} + +\subsection{Was ist Coinflip?} +Das Münzwurf-Spiel 'Coinflip' ist ein klassisches Glücksspiel, das in seiner digitalen Umsetzung den traditionellen Münzwurf simuliert. Das Spiel basiert auf dem einfachen Prinzip einer Münze mit zwei Seiten: Kopf und Zahl. Spieler setzen auf eine der beiden Seiten und haben eine 50\%-ige Gewinnchance. Die Einfachheit des Spiels macht es zu einem idealen Einstiegsspiel für neue Nutzer der Casino-Plattform. + +Im Gegensatz zu komplexeren Spielen wie Dice bietet Coinflip eine feste Gewinnwahrscheinlichkeit von 50\% und einen konstanten Multiplikator von 2x. Dies bedeutet, dass Spieler bei einem Gewinn ihren Einsatz verdoppeln, während sie bei einer Niederlage ihren gesamten Einsatz verlieren. + +\subsubsection{Zufallszahlengenerierung} +Die Implementierung verwendet die Standardklasse java.util.Random zur Generierung des Münzwurfs. +Die Zufallsgenerierung erzeugt einen booleschen Wert, der anschließend einer der beiden Münzseiten zugeordnet wird. +Diese binäre Entscheidung gewährleistet die faire 50:50-Verteilung, die für ein authentisches Münzwurf-Erlebnis erforderlich ist. + +\subsubsection{Spielablauf und Datenfluss} + +Der Spielablauf von Coinflip folgt einem strukturierten Datenfluss zwischen Frontend und Backend. Der Controller empfängt die Spielanfrage mit folgenden Parametern: + +\begin{itemize} + \item \textbf{Einsatz:} Der gesetzte Münzbetrag. + \item \textbf{Gewählte Seite:} Die vom Spieler gewählte Münzseite (Kopf oder Zahl). +\end{itemize} + +Nach dem Erhalt der Anfrage führt der Controller eine Guthabenprüfung durch. Bei ausreichendem Guthaben wird die Anfrage an die Service-Schicht weitergeleitet, andernfalls wird eine entsprechende Fehlermeldung zurückgegeben. + +Die Service-Klasse verarbeitet die Spiellogik in folgender Reihenfolge: +\begin{enumerate} + \item Abbuchung des Einsatzes vom Spielerkonto. + \item Generierung des zufälligen Münzwurfs (Kopf oder Zahl). + \item Vergleich zwischen gewählter Seite und Wurfergebnis. + \item Bei einem Gewinn: Gutschrift des doppelten Einsatzes auf das Spielerkonto. + \item Rückgabe des Spielergebnisses an das Frontend. +\end{enumerate} + +Das Spielergebnis wird strukturiert an das Frontend übermittelt und enthält: +\begin{itemize} + \item Gewinnstatus (gewonnen/verloren) + \item Auszahlungsbetrag (bei Gewinn: 2x Einsatz) + \item Geworfene Münzseite +\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 new file mode 100644 index 0000000..196eb91 --- /dev/null +++ b/projektdokumentation/Inhalt/Deployment.tex @@ -0,0 +1,5 @@ +\section{Deployment} +\label{sec:Deployment} +Es gibt zwei Server auf denen Instanzen der Applikation laufen. +\subsection{\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. +\subsection{\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}). diff --git a/projektdokumentation/Inhalt/Dice.tex b/projektdokumentation/Inhalt/Dice.tex new file mode 100644 index 0000000..465bbcd --- /dev/null +++ b/projektdokumentation/Inhalt/Dice.tex @@ -0,0 +1,43 @@ +\section{Dice} + +\subsection{Was ist Dice?} +Das Würfelspiel 'Dice' ist ein originelles Spiel der Casinoplattform Stake.com\footnote{Stake.com ist eine bekannte Online-Glücksspielplattform, die eine Vielzahl von Casinospielen und Sportwetten anbietet.}. Das Spiel dreht sich um einen virtuellen 100-seitigen Würfel, +bei dem Spieler die Parameter ihrer Wette beeinflussen können. Im Kern geht es darum, +einen zuvor festgelegten 'Roll Over'- oder 'Roll Under'-Betrag zu unter- oder überschreiten, +um eine Runde zu gewinnen. Spieler haben die Kontrolle über den Multiplikator und ihre Gewinnchancen: +Durch die Anpassung des Zielwerts können sie das Verhältnis +von Risiko und potenzieller Auszahlung steuern. Ein höherer Multiplikator verspricht zwar größere Gewinne, +reduziert jedoch gleichzeitig die Wahrscheinlichkeit eines erfolgreichen Würfelwurfs. + +\subsubsection{Zufallszahlengenerierung} +Zur Generierung des Würfelwurfs verwendet diese Implementierung die Standardklasse java.util.Random. +Sie erzeugt eine pseudo-zufällige Zahl zwischen 1 und 100 (inklusive), +die das Ergebnis des virtuellen 100-seitigen Würfels darstellt. + + +\subsubsection{Spielablauf und Datenfluss} + +Der zentrale Controller steuert den Spielablauf und empfängt die Anfragen vom Frontend. Jede Anfrage enthält die Eckdaten des gewünschten Würfelwurfs: +\begin{itemize} + \item \textbf{Einsatz:} Der gesetzte Münzbetrag. + \item \textbf{Wettart:} Soll der Würfel ``über'' oder ``unter'' einen Wert fallen? + \item \textbf{Zielwert:} Der vom Spieler festgelegte Referenzwert (1-100). +\end{itemize} + +Zuerst prüft der Controller das Guthaben des Spielers. Bei unzureichenden Mitteln wird der Vorgang abgelehnt. Andernfalls übergibt er die weitere Ausführung an die Dienstklasse. + +Die Dienstklasse übernimmt die eigentliche Logik des Würfelspiels: +\begin{enumerate} + \item Zieht den Einsatz vom Spielerkonto ab. + \item Erzeugt einen zufälligen Würfelwurf (Wert zwischen 1 und 100). + \item Prüft, ob der Wurf die Gewinnbedingung erfüllt (entsprechend Wettart und Zielwert). + \item Berechnet die Gewinnwahrscheinlichkeit und den sich daraus ergebenden Multiplikator. + \item Schreibt bei einem Gewinn den entsprechenden Betrag (Einsatz $\times$ Multiplikator) dem Spielerkonto gut. +\end{enumerate} + +Das Ergebnis des Spiels wird an das Frontend zurückgesendet und enthält: +\begin{itemize} + \item Gewinnstatus (gewonnen/verloren) + \item Auszahlungsbetrag + \item Gewürfelten Wert +\end{itemize} diff --git a/projektdokumentation/Inhalt/Einleitung.tex b/projektdokumentation/Inhalt/Einleitung.tex new file mode 100644 index 0000000..1ec4b88 --- /dev/null +++ b/projektdokumentation/Inhalt/Einleitung.tex @@ -0,0 +1,57 @@ +% !TEX root = ../Projektdokumentation.tex +\section{Einleitung} +\label{sec:Einleitung} +Die folgende Projektdokumentation beschreibt die Entwicklung einer innovativen Online-Casino-Plattform für die HiTec GmbH, +ein mittelgroßes IT-Systemhaus mit Sitz in Bremen, das seit über 15 Jahren als IT-Dienstleister tätig und seit einigen Jahren ISO/IEC 27001 zertifiziert ist. Das Kerngeschäft der HiTec GmbH umfasst Entwicklung eigener Softwareprodukte, Consulting, IT-Systembereich sowie Support und Wartung und beschäftigt spezialisierte Mitarbeiter in verschiedenen Abteilungen. + +\subsection{Projektumfeld} +\label{sec:Projektumfeld} +Auftraggeber des Projektes ist Herr Hofmann, der Inhaber der HiTec GmbH. +Das Unternehmen möchte sein Produkt-Portfolio in den nächsten zwei Jahren wesentlich erweitern, um weiterhin am Markt zu bestehen und noch stärker zu wachsen. Dazu soll der Bereich der Webanwendungen mit einem neuartigen Produkt beitragen, das über einzigartige Features verfügt. + +Das Ziel ist es, das fachliche Know-How des Entwicklungsteams stärker zu nutzen, um als innovatives IT-Unternehmen langfristig am Markt wahrgenommen zu werden und schnell Erträge zu erzielen. + +Die technische Umsetzung erfolgt mit den in der HiTec GmbH etablierten Technologien und Projektmanagement-Methoden unter Verwendung agiler Entwicklungsmethoden. + +\subsection{Projektziel} +\label{sec:Projektziel} +Ziel des Projektes ist die Entwicklung einer innovativen Online-Casino-Plattform als neuartiges Produkt für das Portfolio der HiTec GmbH. + +Die Plattform soll verschiedene Casino-Spiele wie Blackjack, Coinflip, Würfelspiele und Spielautomaten anbieten und durch moderne Features wie Lootboxes und ein umfassendes Benutzermanagement erweitert werden. +Das System soll eine vollständige Transaktionsabwicklung mit Einzahlungsfunktionen und eine sichere Benutzerauthentifizierung über OAuth2-Integration bieten. + +Durch den Einstieg in den Entertainment-Bereich soll das bisherige IT-Systemhaus-Portfolio um ein profitables Gaming-Produkt erweitert werden. +Die Fokussierung auf Social Casino Games ermöglicht den Zugang zum wachsenden Markt der Casual-Gamer, während gleichzeitig die strengen Glücksspiellizenzen und regulatorischen Anforderungen umgangen werden können. + +Die Monetarisierung erfolgt primär über den Verkauf virtueller Währungen und Premium-Features, was bei vergleichsweise niedrigen Entwicklungskosten eine hohe Gewinnmarge verspricht. +Zusätzliche Einnahmequellen werden durch In-App-Käufe und spezielle Lootbox-Angebote generiert. + +Diese Webanwendung soll als hochproduktive und kostenarme Implementierung realisiert werden, um schnell Marktreife zu erlangen und Erträge zu generieren. + +\subsection{Projektbegründung} +\label{sec:Projektbegruendung} +Der Online-Gaming- und Casino-Markt zeigt kontinuierliches Wachstum und bietet erhebliches Potenzial für innovative Lösungen. +Durch die Entwicklung einer modernen Casino-Plattform kann die HiTec GmbH eine neue Kundengruppe erschließen und eine Nische im Gaming-Bereich besetzen. + +Die Verwendung bewährter Technologien (Java mit Spring-Boot, Angular, Keycloak) minimiert die Entwicklungsrisiken und ermöglicht eine schnelle Markteinführung. +Gleichzeitig demonstriert das Projekt die technische Kompetenz des Unternehmens in der Entwicklung komplexer, interaktiver Webanwendungen. + +Ein modularer Aufbau mit verschiedenen Spielen und Features ermöglicht eine schrittweise Erweiterung und Anpassung an Marktanforderungen, was langfristige Wartbarkeit und Skalierbarkeit gewährleistet. + +\subsection{Projektschnittstellen} +\label{sec:Projektschnittstellen} +Die Anwendung wird als Full-Stack-Webanwendung mit getrenntem Backend und Frontend entwickelt. +Das Backend basiert auf Java mit Spring-Boot und stellt RESTful APIs für die Spiellogik, Benutzerverwaltung und Transaktionsabwicklung bereit. + +Für die Benutzerauthentifizierung wird Keycloak als Identity Provider eingesetzt, der OAuth2-Integration mit externen Anbietern wie Google und GitHub ermöglicht. +Die Frontend-Anwendung wird mit Angular entwickelt und kommuniziert über HTTP-APIs mit dem Backend. + +Zur Containerisierung und für das Deployment wird Docker verwendet, was eine konsistente Entwicklungs- und Produktionsumgebung gewährleistet. +Die Projektverwaltung erfolgt mit Jira unter Verwendung der Scrum-Methodik, die in der HiTec GmbH als Standard etabliert ist. + +\subsection{Projektabgrenzung} +\label{sec:Projektabgrenzung} +Da der Projektumfang beschränkt ist, konzentriert sich die Entwicklung auf die Kernfunktionalitäten der Casino-Plattform. +Erweiterte Features wie Live-Dealer-Spiele, komplexe Turniere oder Integration mit externen Zahlungsdienstleistern sind nicht Bestandteil dieses Mittelstufenprojekts. + +Die Implementierung beschränkt sich auf eine begrenzte Anzahl von Casino-Spielen und grundlegende Benutzerfunktionen, um eine funktionsfähige Proof-of-Concept-Anwendung zu erstellen. diff --git a/projektdokumentation/Inhalt/Lootboxes.tex b/projektdokumentation/Inhalt/Lootboxes.tex new file mode 100644 index 0000000..8f4860c --- /dev/null +++ b/projektdokumentation/Inhalt/Lootboxes.tex @@ -0,0 +1,78 @@ +\section{Lootboxes} + +\subsection{Was sind Lootboxes?} +Lootboxes sind digitale Belohnungsbehälter, die gegen eine feste Gebühr geöffnet werden können und dem Spieler einen zufälligen Gewinn basierend auf vordefinierten Wahrscheinlichkeiten gewähren. Das System orientiert sich an bekannten Gaming-Mechaniken und bietet Spielern die Möglichkeit, durch Glück höhere Gewinne zu erzielen als den bezahlten Einsatz. Jede Lootbox enthält verschiedene Belohnungen mit unterschiedlichen Gewinnwahrscheinlichkeiten, wodurch ein Spannungselement entsteht. + +Das Lootbox-System der Casino-Plattform umfasst sowohl die Verwaltung verschiedener Lootbox-Typen als auch ein animiertes Öffnungserlebnis, das an populäre Spiele wie Counter-Strike erinnert. Spieler können zwischen verschiedenen Lootboxes wählen, die sich in Preis und Belohnungsstruktur unterscheiden. + +\subsubsection{Wahrscheinlichkeitssystem und Belohnungsverteilung} +Das Herzstück des Lootbox-Systems ist der Wahrscheinlichkeitsalgorithmus, der faire und nachvollziehbare Belohnungsverteilungen gewährleistet. Jede Belohnung in einer Lootbox hat eine definierte Wahrscheinlichkeit zwischen 0,0 und 1,0 (0\% bis 100\%). Die Gewinnermittlung erfolgt durch einen kumulativen Wahrscheinlichkeitsansatz: + +\begin{enumerate} + \item Generierung einer Zufallszahl zwischen 0 und 1 + \item Iteration durch alle Belohnungen mit Akkumulation der Wahrscheinlichkeiten + \item Rückgabe der ersten Belohnung, bei der die Zufallszahl kleiner oder gleich der kumulativen Wahrscheinlichkeit ist + \item Fallback zur letzten Belohnung bei keiner Übereinstimmung (Sicherheitsmechanismus) +\end{enumerate} + +Dieses System ermöglicht eine präzise Kontrolle über die Verteilung der Belohnungen und gewährleistet, dass seltene Gegenstände entsprechend ihrer konfigurierten Wahrscheinlichkeit auftreten. + +\subsubsection{Seltenheitssystem} +Die Lootboxes verwenden ein sechsstufiges Seltenheitssystem, das visuell durch Farbkodierung dargestellt wird: + +\begin{itemize} + \item \textbf{Common (Häufig):} 40\%+ Wahrscheinlichkeit - Blau + \item \textbf{Uncommon (Ungewöhnlich):} 20-40\% Wahrscheinlichkeit - Grün + \item \textbf{Rare (Selten):} 10-20\% Wahrscheinlichkeit - Blau + \item \textbf{Epic (Episch):} 5-10\% Wahrscheinlichkeit - Lila + \item \textbf{Legendary (Legendär):} 1-5\% Wahrscheinlichkeit - Gold + \item \textbf{Mythic (Mythisch):} <1\% Wahrscheinlichkeit - Rot +\end{itemize} + +Die Farbkodierung wird sowohl in der Auswahlansicht als auch während der Öffnungsanimation verwendet, um Spielern sofort die Seltenheit ihrer Belohnung zu kommunizieren. + +\subsubsection{Animiertes Öffnungserlebnis} +Das Frontend bietet ein hochgradig animiertes Öffnungserlebnis, das die Spannung und das Engagement der Spieler steigert. Die Öffnungssequenz umfasst: + +\begin{enumerate} + \item \textbf{Auswahlphase:} Rasteranzeige verfügbarer Lootboxes mit Preis und möglichen Belohnungen + \item \textbf{Öffnungsanimation:} 10-sekündige horizontale Scrollanimation mit 120 Gegenständen + \item \textbf{Gewinnermittlung:} Hervorhebung des Gewinnergegenstands an Position 60 + \item \textbf{Ergebnisdarstellung:} Einblendanimation für Gewinn und Gewinn-/Verlustberechnung +\end{enumerate} + +Die Animation generiert eine Walze mit 120 Gegenständen, wobei der tatsächliche Gewinn immer an der mittleren Position (60) platziert wird. Dies erzeugt den Eindruck einer echten Zufallsauswahl, obwohl das Ergebnis bereits vorher feststeht. + +\subsubsection{Datenmodell und Verwaltung} +Das Backend verwendet ein flexibles Datenmodell, das die Erstellung und Verwaltung verschiedener Lootbox-Typen ermöglicht: + +\textbf{LootBoxEntity:} Repräsentiert eine Lootbox mit Namen, Preis und zugehörigen Belohnungen. Die Beziehung zu Belohnungen erfolgt über eine Viele-zu-Viele-Verknüpfung, wodurch Belohnungen zwischen verschiedenen Lootboxes geteilt werden können. + +\textbf{RewardEntity:} Definiert einzelne Belohnungen mit Wert und Wahrscheinlichkeit. Die Wahrscheinlichkeit wird als Dezimalzahl mit einer Genauigkeit von 5 Stellen und 2 Nachkommastellen gespeichert. + +Die Verwaltung erfolgt über REST-Endpunkte, die sowohl das Abrufen verfügbarer Lootboxes als auch das Kaufen und Öffnen ermöglichen. Administrative Funktionen erlauben das Erstellen neuer Lootboxes und das Löschen bestehender. + +\subsubsection{Kaufprozess und Guthaben-Integration} +Der Lootbox-Kaufprozess ist nahtlos in das Guthaben-Management-System der Plattform integriert: + +\begin{enumerate} + \item Validierung des Benutzerguthabens gegen den Lootbox-Preis + \item Sofortige lokale Guthabenabzug für responsives Benutzererlebnis + \item Backend-API-Aufruf zur Kaufabwicklung + \item Server-seitige Belohnungsermittlung mittels Wahrscheinlichkeitsalgorithmus + \item Guthaben-Update (Preis abziehen, Belohnung hinzufügen) + \item Frontend-Synchronisation und Animationsstart +\end{enumerate} + +Das System gewährleistet durch transaktionale Datenbankoperationen, dass Guthaben-Updates atomisch erfolgen und keine inkonsistenten Zustände entstehen können. + +\subsubsection{Sicherheit und Fairness} +Die Lootbox-Implementierung legt besonderen Wert auf Sicherheit und Fairness: + +\textbf{Server-seitige Autorität:} Alle Belohnungsermittlungen erfolgen ausschließlich auf dem Server, wodurch Client-seitige Manipulationen ausgeschlossen werden. + +\textbf{Eingabevalidierung:} Sowohl die Datenübertragungsobjekte als auch die Backend-Services validieren alle Eingaben auf Korrektheit und Bereichsgrenzen. + +\textbf{Wahrscheinlichkeitsintegrität:} Das System überwacht die Wahrscheinlichkeitswerte und gewährleistet, dass sie im gültigen Bereich (0,0-1,0) liegen. + +Die Implementierung bietet somit ein vollständiges, produktionsreifes Lootbox-System mit robusten Frontend-Animationen, ordnungsgemäßer Backend-Validierung und nahtloser Benutzerintegration. \ No newline at end of file diff --git a/projektdokumentation/Inhalt/Projektarchitektur.tex b/projektdokumentation/Inhalt/Projektarchitektur.tex new file mode 100644 index 0000000..4480dd6 --- /dev/null +++ b/projektdokumentation/Inhalt/Projektarchitektur.tex @@ -0,0 +1,40 @@ +% !TEX root = ../Projektdokumentation.tex +\section{Projektarchitektur} +\label{sec:Projektarchitektur} + +\subsection{Überblick} +Das Casino Gaming Platform Projekt folgt einer klassischen Client-Server-Architektur mit einer klaren Trennung zwischen Frontend und Backend. Diese Architektur wurde gewählt, um eine saubere Separation of Concerns zu gewährleisten und die Wartbarkeit sowie Erweiterbarkeit des Systems zu fördern. Die Kommunikation zwischen den beiden Schichten erfolgt über REST-\acs{API}s, die \acs{JSON}-Daten austauschen. + +\subsection{Technologie-Stack} + +\subsubsection{Frontend-Technologien} +Für die Entwicklung der Benutzeroberfläche wurde Angular\footnote{Angular - \url{https://angular.io/}} als Framework gewählt. Angular bietet eine robuste Basis für Single Page Applications und ermöglicht eine komponentenbasierte Entwicklung. Als Package Manager kommt Bun\footnote{Bun - \url{https://bun.sh/}} zum Einsatz, welcher sowohl die Paketinstallation als auch das Bundling übernimmt. Für das Styling wird Tailwind CSS\footnote{Tailwind CSS - \url{https://tailwindcss.com/}} verwendet, welches eine konsistente und effiziente Gestaltung der Benutzeroberfläche ermöglicht. + +Für die Qualitätssicherung werden \acs{E2E}-Tests mit Playwright\footnote{Playwright - \url{https://playwright.dev/}} durchgeführt. Diese Tests stellen sicher, dass die gesamte Anwendung aus Benutzersicht korrekt funktioniert. + +\subsubsection{Backend-Technologien} +Das Backend basiert auf Spring Boot, einem Java-Framework, das eine schnelle Entwicklung von produktionsreifen Anwendungen ermöglicht. Spring Boot wurde gewählt, da es umfangreiche Funktionalitäten out-of-the-box bietet und sich durch eine starke Community-Unterstützung auszeichnet. Als Build-Tool kommt Gradle zum Einsatz, welches flexiblere Konfigurationsmöglichkeiten als Maven bietet. + +Für die Datenpersistierung wird PostgreSQL\footnote{PostgreSQL - \url{https://www.postgresql.org/}} verwendet. + +\subsection{Systemarchitektur} + +\subsubsection{Frontend-Architektur} +Das Frontend wurde als Single Page Application (SPA) konzipiert, um eine flüssige Benutzererfahrung zu gewährleisten. Die Architektur folgt Angulars modularem Ansatz und gliedert sich in verschiedene Bereiche: Feature-Module organisieren die Funktionalitäten nach Geschäftsbereichen wie Spiele und Einzahlungen. Wiederverwendbare UI-Komponenten wurden in einem Shared-Bereich zusammengefasst, um Code-Duplikation zu vermeiden. + +Services übernehmen die Kommunikation mit dem Backend und kapseln die Geschäftslogik. \acs{HTTP}-Interceptors behandeln globale Aspekte wie Fehlerbehandlung zentral. + +\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. 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. + +Der Financial Bereich behandelt alle monetären Transaktionen, Guthaben und Einzahlungen mit entsprechenden Audit-Trails für Compliance-Zwecke. Das Loot System verwaltet Lootboxen, deren Belohnungen und die zugehörigen Wahrscheinlichkeitsverteilungen, wobei besonderer Wert auf Transparenz und Fairness gelegt wird. + +\subsection{Deployment-Strategie} +Die Deployment-Strategie wurde so konzipiert, dass sie sowohl lokale Entwicklung als auch produktive Umgebungen unterstützt. Für die lokale Entwicklung wird Docker Compose\footnote{Docker Compose - \url{https://docs.docker.com/compose/}} eingesetzt, welches eine konsistente Entwicklungsumgebung über verschiedene Entwicklerrechner hinweg gewährleistet. + +Frontend und Backend können unabhängig voneinander gebaut und deployed werden, was eine flexible Entwicklung und Wartung ermöglicht. Diese Entkopplung erlaubt es verschiedenen Teams, parallel zu arbeiten, ohne sich gegenseitig zu blockieren. Verschiedene Umgebungskonfigurationen durch Profile stellen sicher, dass entwicklungs- und produktionsspezifische Einstellungen sauber getrennt sind und automatisiert angewendet werden können. \ No newline at end of file diff --git a/projektdokumentation/Inhalt/Slots.tex b/projektdokumentation/Inhalt/Slots.tex new file mode 100644 index 0000000..3cdb716 --- /dev/null +++ b/projektdokumentation/Inhalt/Slots.tex @@ -0,0 +1,56 @@ +\section{Slots} + +\subsection{Was ist Slots?} +Das Spiel 'Slots' ist eine digitale Implementierung eines klassischen Spielautomaten. Spieler setzen einen Betrag und drehen die Walzen, um Kombinationen von Symbolen zu erhalten. Ziel ist es, drei gleiche Symbole auf der mittleren Gewinnlinie zu erzielen, um einen Gewinn basierend auf dem Multiplikator des Symbols zu erhalten. Das Spiel bietet eine einfache und intuitive Möglichkeit, Glücksspiel zu erleben. + +\subsubsection{Zufallszahlengenerierung} +Zur Generierung der Walzenpositionen verwendet diese Implementierung die Standardklasse \texttt{java.util.Random}. Für jede der drei Walzen wird eine pseudo-zufällige Zahl zwischen 0 und der \texttt{REEL\_LENGTH} (32) generiert. Diese Zahlen bestimmen die Startposition jedes Walzenstreifens. Die Walzenstreifen selbst werden bei der Initialisierung des \texttt{SlotService} einmalig erstellt und dann zufällig gemischt, um eine faire Verteilung der Symbole zu gewährleisten. + +\subsubsection{Symbole und Auszahlungen} +Das Slots-Spiel verwendet verschiedene Symbole, die jeweils einen spezifischen Auszahlungsmultiplikator und eine bestimmte Häufigkeit auf den Walzenstreifen haben. Die Symbole sind: +\begin{itemize} + \item \textbf{SEVEN:} Höchster Auszahlungsmultiplikator (\texttt{1000x}). Seltenstes Symbol auf den Walzen. + \item \textbf{BAR:} Hoher Auszahlungsmultiplikator (\texttt{85x}). + \item \textbf{BELL:} Mittlerer Auszahlungsmultiplikator (\texttt{40x}). + \item \textbf{CHERRY:} Niedriger Auszahlungsmultiplikator (\texttt{10x}). Häufiger auf den Walzen. + \item \textbf{BLANK:} Kein Auszahlungsmultiplikator (\texttt{0x}). Dient als Platzhalter und führt bei dreifachem Erscheinen zu keinem Gewinn. +\end{itemize} +Die genaue Verteilung der Symbole auf den Walzenstreifen (\texttt{REEL\_LENGTH = 32}) ist wie folgt: +\begin{itemize} + \item SEVEN: 1 Vorkommen + \item BAR: 4 Vorkommen + \item BELL: 7 Vorkommen + \item CHERRY: 10 Vorkommen + \item BLANK: 10 Vorkommen +\end{itemize} + +\subsubsection{Spielablauf und Datenfluss} + +Der zentrale \texttt{SlotController} steuert den Spielablauf und empfängt die Anfragen vom Frontend. Jede Anfrage zum Drehen der Walzen (\texttt{/slots/spin}) enthält die Eckdaten des gewünschten Spins: +\begin{itemize} + \item \textbf{Einsatz:} Der gesetzte Münzbetrag. +\end{itemize} + +Zuerst prüft der Controller das Guthaben des Spielers. Bei unzureichenden Mitteln wird der Vorgang abgelehnt. Andernfalls übergibt er die weitere Ausführung an die Dienstklasse (\texttt{SlotService}). + +Die Dienstklasse (\texttt{SlotService}) übernimmt die eigentliche Logik des Slots-Spiels: +\begin{enumerate} + \item Zieht den Einsatz vom Spielerkonto ab. + \item Generiert zufällige Startpositionen für jede der drei Walzen. + \item Ermittelt die Symbole auf der mittleren Gewinnlinie basierend auf den generierten Positionen. + \item Bestimmt den Status des Spins (\texttt{WIN}, \texttt{LOSE}, \texttt{BLANK}) basierend auf den ermittelten Symbolen: + \begin{itemize} + \item \texttt{WIN}: Alle drei Symbole auf der Gewinnlinie sind identisch und keines davon ist \texttt{BLANK}. + \item \texttt{BLANK}: Alle drei Symbole auf der Gewinnlinie sind identisch und es handelt sich um \texttt{BLANK}-Symbole. Dies führt zu keinem Gewinn. + \item \texttt{LOSE}: Die Symbole auf der Gewinnlinie sind nicht alle identisch. + \end{itemize} + \item Berechnet den Gewinnbetrag bei einem \texttt{WIN}-Status (Einsatz $\times$ Auszahlungsmultiplikator des Gewinnsymbols) und schreibt ihn dem Spielerkonto gut. Bei \texttt{LOSE} oder \texttt{BLANK} wird kein Betrag gutgeschrieben. + \item Erstellt eine 3x3-Matrix der sichtbaren Symbole auf den Walzen, zentriert um die Gewinnlinie. +\end{enumerate} + +Das Ergebnis des Spiels wird an das Frontend zurückgesendet und enthält: +\begin{itemize} + \item \textbf{Status:} Der Status des Spins (\texttt{win}, \texttt{lose}, \texttt{blank}). + \item \textbf{Amount:} Der gewonnene Betrag (bei \texttt{WIN}) oder der Einsatz (bei \texttt{LOSE}/\texttt{BLANK}). + \item \textbf{Result Matrix:} Eine 3x3-Matrix der Symbole, die auf den Walzen sichtbar sind. +\end{itemize} diff --git a/projektdokumentation/Inhalt/Vorbereitung.tex b/projektdokumentation/Inhalt/Vorbereitung.tex new file mode 100644 index 0000000..0f74afe --- /dev/null +++ b/projektdokumentation/Inhalt/Vorbereitung.tex @@ -0,0 +1,27 @@ +\section{Vorbereitung} +\label{sec:Vorbereitung} + +\subsection{Ist-Analyse} +Im Rahmen der durchgeführten Marktanalyse konnten mehrere relevante Marktlücken identifiziert werden, die das geplante Produkt gezielt adressieren kann. Ein zentrales Defizit betrifft die technologische Basis vieler bestehender Casino-Plattformen. Zahlreiche Anbieter setzen noch auf veraltete Technologien, was sich nachteilig auf Performance, Wartbarkeit und Erweiterbarkeit auswirkt. Moderne Webtechnologien und Frameworks könnten in diesem Zusammenhang klare Vorteile schaffen, etwa durch schnellere Ladezeiten und verbesserte Skalierbarkeit. + +Im Bereich der Benutzerfreundlichkeit bestehen Defizite. Viele Plattformen bieten eine unzureichende mobile Nutzererfahrung oder verlangen komplexe, zeitaufwändige Registrierungsprozesse – oft ohne Social-Login oder einfache Authentifizierung. Ein Beispiel hierfür ist der Zwang zu langwierigen Formularen, die Nutzer gerade auf mobilen Geräten häufig frustrieren und zum Abbruch des Vorgangs führen. + +Ein weiteres Problem liegt bei den Sicherheitsstandards. In zahlreichen Anwendungen sind moderne Authentifizierungsverfahren wie OAuth2 oder Zwei-Faktor-Authentifizierung noch nicht implementiert. Dies hat nicht nur zur Folge, dass das Vertrauen der Nutzer geschwächt wird, sondern öffnet auch potenzielle Sicherheitslücken. + +Schließlich bestehen Defizite in der Integration zeitgemäßer Zahlungsprozesse. Oft sind Einzahlungen umständlich und erfordern manuelle Schritte, da moderne Payment-APIs wie Stripe, Apple Pay oder Google Pay nicht eingebunden sind. Ein reibungsloser, mobiltauglicher Zahlungsfluss könnte sich in diesem Zusammenhang als entscheidender Wettbewerbsvorteil erweisen. + +\subsection{Soll-Analyse} +Für das geplante Online-Casino wurde eine umfassende Soll-Analyse durchgeführt, die die zentralen funktionalen, technischen und sozialen Anforderungen beschreibt. + +Im Bereich des Spieleportfolios ist eine ausgewogene Mischung aus klassischen und unterhaltsamen Spielen vorgesehen. Zu den angebotenen Spielen zählen Blackjack mit erweiterten Spieloptionen wie Split und Double Down sowie verschiedene Slotmaschinen, deren RTP (Return to Player) flexibel konfigurierbar sein soll. Das Angebot wird durch einfache Zufallsspiele wie Dice, Coinflip sowie unterhaltungsorientierte Features wie Lootboxen, Plinko und Poker ergänzt, um unterschiedliche Spieltypen anzusprechen und die Nutzer langfristig zu binden. + +Die Benutzerverwaltung basiert auf einem modernen, sicheren Authentifizierungssystem. Die Anmeldung erfolgt über eine Multi-Provider-OAuth2-Lösung, beispielsweise mit GitHub oder Google-Konten. Die Sitzungsverwaltung erfolgt über JWT-basierte Tokens, ergänzt durch zusätzliche Sicherheitsmechanismen wie E-Mail-Verifizierung und die Möglichkeit zum Passwort-Reset, um Datenschutz und Benutzerkomfort gleichermaßen zu gewährleisten. + +Das Finanzsystem ist auf Einzahlungen in Echtzeit ausgerichtet. Zu diesem Zweck wird eine Stripe-Integration implementiert, die einen reibungslosen und sicheren Zahlungsprozess ermöglicht. Intern erfolgt die Verwaltung der Guthaben über eine virtuelle Währung, deren Genauigkeit mithilfe von BigDecimal präzise nachvollziehbar bleibt. Eine lückenlos nachvollziehbare Transaktionshistorie mit Status-Tracking gewährleistet maximale Transparenz und Nachvollziehbarkeit aller Zahlungsvorgänge. + +Gemäß den technischen Anforderungen ist ein responsives Design mit Fokus auf Mobile-First-Nutzung erforderlich, um eine hohe Benutzerfreundlichkeit auf verschiedenen Plattformen zu gewährleisten. Echtzeitaktualisierungen der Inhalte sollen ohne erneuten Seitenaufbau erfolgen, um eine nahtlose und dynamische Spielerfahrung zu gewährleisten. Es ist essenziell, dass sämtliche API-Kommunikation über gesicherte Schnittstellen abgewickelt wird, um die Integrität der Daten und den Schutz sensibler Informationen sicherzustellen. + +Schließlich spielen auch die sozialen Funktionen eine wichtige Rolle. Ranglisten, eine motivierende In-Game-Währung sowie ein Fokus auf Transparenz im Umgang mit Daten, Spielverläufen und Zahlungsflüssen sollen die Community stärken, die Nutzerbindung fördern und das Vertrauen in das Produkt nachhaltig unterstützen. Das Produkt wird als moderne Webanwendung direkt über den Browser bereitgestellt und ist somit ohne Download oder Installation für den Nutzer zugänglich. Um das Online-Casino nutzen zu können, sind einige technische Voraussetzungen erforderlich. Für die Anwendung wird ein aktueller Webbrowser wie Google Chrome, Mozilla Firefox, Safari oder Microsoft Edge vorausgesetzt. Zudem ist JavaScript aktiviert sein, da dieses für die Darstellung der Spielmechanik und der Benutzeroberfläche essenziell ist. Für eine reibungslose Kommunikation mit dem Server und ein flüssiges Spielerlebnis ist eine stabile Internetverbindung erforderlich. Zusätzlich ist die Erlaubnis von Cookies erforderlich, um Sitzungsdaten zu speichern und personalisierte Inhalte oder Nutzertracking zu ermöglichen. + +Die genannten technischen Voraussetzungen bilden die Grundlage für einen stabilen und benutzerfreundlichen Zugriff auf die Anwendung. Abgesehen von den Basiskomponenten ergeben sich jedoch weiterführende Anforderungen an die Systemarchitektur, insbesondere im Hinblick auf die Sicherheit und die Fairness der Spielergebnisse. +Eine zentrale Herausforderung bei der technischen Umsetzung des Online-Casinos liegt in der sicheren und manipulationssicheren Spielauswertung. Um die Integrität des Spiels zu gewährleisten und Manipulationen durch Nutzer im Frontend zu verhindern, müssen sämtliche Spielmechaniken serverseitig ausgewertet werden. Dies bedeutet, dass Zufallsberechnungen, Spielergebnisse und Gewinnermittlungen ausschließlich auf dem Server erfolgen, bevor sie an das Frontend übermittelt werden. Diese Architektur gewährleistet, dass die Spielchancen stets fair und unveränderbar bleiben. Gleichzeitig erfordert sie eine besonders zuverlässige Server-Infrastruktur, da sie sowohl in Echtzeit als auch mit hoher Verfügbarkeit arbeiten muss. Diese Anforderung stellt eine bedeutsame technische Einschränkung dar, die bei der Systemarchitektur, der Performanceoptimierung sowie der Skalierung von Beginn an zu berücksichtigen ist. \ No newline at end of file diff --git a/projektdokumentation/Inhalt/WirtschaftlicheBetrachtung.tex b/projektdokumentation/Inhalt/WirtschaftlicheBetrachtung.tex new file mode 100644 index 0000000..4790ed2 --- /dev/null +++ b/projektdokumentation/Inhalt/WirtschaftlicheBetrachtung.tex @@ -0,0 +1,67 @@ +\section{Wirtschaftliche Betrachtung} +\subsection{Marktuntersuchung} +Das Online-Casino fokussiert sich in erster Linie auf eine junge, technikaffine Zielgruppe im Alter von 18 bis 35 Jahren. Diese Nutzergruppe verfügt über Erfahrungen mit digitalen Anwendungen, zeigt eine hohe Bereitschaft für Internetaktivitäteten und ist für neue, spielerische Unterhaltungsformate insbesondere im Bereich des Web-Gamings aufgeschlossen. Sie sucht nach kurzweiligen, optisch ansprechenden Angeboten, die jederzeit und ohne große Einstiegshürde zugänglich sind. +Als sekundäre Zielgruppe werden Gelegenheitsspieler angesprochen, die besonderen Wert auf eine einfache, benutzerfreundliche Bedienung legen. Für diese Nutzer steht nicht die Technik, sondern das unkomplizierte Spielvergnügen im Vordergrund. Die Nutzerinnen und Nutzer erwarten eine klare Navigation, intuitive Spielmechaniken und möglichst wenig Hürden bei Registrierung, Einzahlung und Nutzung. Durch die Fokussierung auf zwei Zielgruppen soll eine breite Nutzerbasis aufgebaut werden, die sowohl regelmäßige als auch spontane Spieler umfasst. + +Die Erwartungshaltung dieser Zielgruppe umfasst ein unterhaltsames, fesselndes und optisch ansprechendes Spielerlebnis, das eine authentische Casino-Atmosphäre vermittelt. Dabei legen die Nutzer großen Wert auf eine intuitive Benutzeroberfläche, schnelle Ladezeiten sowie abwechslungsreiche Spiele wie Slots, Blackjack oder CoinFlip. Gleichzeitig legen sie großen Wert auf einen reibungslosen und sicheren Zugang zum Spiel. Aus diesem Grund erwarten sie selbstverständlich moderne Mechanismen für eine zuverlässige und geschützte Anmeldung und Nutzung. + +Die Erwartungen der Zielgruppe geben wichtige Hinweise darauf, welche Funktionen und Eigenschaften ein erfolgreiches Produkt aufweisen muss. Um die gestellten Erwartungen jedoch auch unter wirtschaftlichen Gesichtspunkten bedienen zu können, ist eine Analyse des Marktvolumens und -potenzials unerlässlich. +Der Markt für Social Casinos und Online-Casinos mit Auszahlungsverweigerung ist weltweit wachsend. Laut Branchenanalysen (z.B. Statista, Business Research Insights) liegt das Volumen des globalen Social-Casino-Markts bei mehreren Milliarden USD pro Jahr, mit jährlichen Wachstumsraten im hohen einstelligen Prozentbereich. In Zielmärkten wie den USA, Europa und Teilen Asiens ist das Potenzial besonders hoch, insbesondere bei Nutzern, die Glücksspiel aus rechtlichen oder moralischen Gründen meiden, aber den Spielspaß schätzen. + +Das vorhandene Absatzpotential ist jedoch stark eingeschränkt auf eine klar umrissene Zielgruppe. Es richtet sich an Nutzer, die Glücksspiele primär als Entertainment betrachten und sich der Tatsache bewusst sind, dass keine Auszahlung möglich ist. In diesem Segment könnte eine emotionale Markenbindung ähnlich wie bei Gaming- oder Streaming-Abonnements aufgebaut werden. Nichtsdestotrotz ist das Marktvolumen im Vergleich zu klassischen Casino-Plattformen begrenzt, da der Reiz vieler Online-Casinos im potenziellen Gewinn liegt. + +Die Wettbewerbssituation für das geplante Online-Casino ohne Auszahlungsoption ist als intensiv und dynamisch zu bezeichnen, da sich das Produkt in einem stark umkämpften digitalen Unterhaltungsmarkt bewegt. Das Wettbewerbsumfeld wird dabei von zwei Hauptarten von Konkurrenzprodukten geprägt. Einerseits klassische Online-Casinos mit Echtgeld-Auszahlung, die aufgrund der Aussicht auf reale Geldgewinne für viele Nutzer besonders attraktiv sind. Es sei darauf hingewiesen, dass renommierte Anbieter wie 888casino, LeoVegas, Stake oder Tipico Casino in der Regel staatlich lizenziert sind, ein umfangreiches Spielangebot offerieren und von hoher Markenbekanntheit sowie intensiven Marketingaktivitäten profitieren. Andererseits existieren Social Casinos ohne Auszahlungsfunktion, die in direkter Konkurrenz zum geplanten Produkt stehen. Plattformen wie Slotomania, House of Fun, Big Fish Casino oder DoubleDown Casino ermöglichen den Einsatz von Echtgeld, bieten jedoch keine reale Auszahlung. Stattdessen liegt der Fokus auf Unterhaltung, Gamification, täglichen Belohnungen, virtuellen Währungen und exklusiven VIP-Features. Diese Angebote sind in der Regel als Apps verfügbar, verfügen über eine große Nutzerbasis und arbeiten mit durchdachten Mechaniken zur langfristigen Nutzerbindung. + +Die enorme Verbreitung und Beliebtheit dieser Social-Casino-Plattformen wirft die Frage auf, welche konkreten Stärken und Schwächen diese bestehenden Angebote aufweisen. Ein näherer Blick auf die Vor- und Nachteile der Konkurrenzprodukte liefert wichtige Erkenntnisse für die strategische Ausrichtung. +So ist liegt ein zentraler Vorteil bestehender Social-Casino-Produkte in der transparenten Erwartungshaltung der Nutzer. Da keine Auszahlungen von Echtgeld vorgesehen sind, besteht kein Missverständnis über mögliche Gewinne. Zudem unterliegen diese Angebote häufig geringeren regulatorischen Anforderungen, da sie rechtlich nicht als klassisches Glücksspiel eingestuft werden und somit oft keine spezielle Lizenz benötigen. Der gezielte Einsatz von Gamification-Elementen und sozialen Funktionen wie Ranglisten oder täglichen Herausforderungen führt auf vielen dieser Plattformen zu einer hohen Nutzerbindung. +Gleichzeitig sind bei diesen Modellen gewisse Nachteile zu berücksichtigen. Da reale Gewinne ausgeschlossen sind, ist die Zahlungsbereitschaft der Nutzer in der Regel geringer. Wird das Belohnungssystem als wenig attraktiv oder zu repetitiv wahrgenommen, sinkt die Verweildauer auf der Plattform schnell. Des Weiteren bedienen sich zahlreiche dieser Produkte sogenannter Pay-to-Win-Mechaniken, welche den zahlenden Nutzern Vorteile gegenüber anderen Teilnehmern verschaffen. Dieser Umstand resultiert bei der Spielerschaft häufig in Frustration und Unzufriedenheit. +Um sich in dieser Wettbewerbslandschaft zu behaupten, muss sich das neue Produkt durch klare Kommunikation, hohe Benutzerfreundlichkeit, attraktive Preisgestaltung sowie ein überzeugendes Unterhaltungs- und Belohnungssystem von der Masse abzuheben, um sich erfolgreich zu behaupten. Die Transparenz über den Verzicht auf Auszahlungen sowie eine hochwertige, faire Spielerfahrung sind von entscheidender Bedeutung, um Vertrauen aufzubauen und langfristige Nutzerbindung zu erreichen. + +Obwohl das geplante Geschäftsmodell einige Vorteile und einen innovativen Ansatz bietet, sollten auch dessen potenzielle Risiken nicht außer Acht gelassen werden. Rechtlich besteht die Möglichkeit, dass das Angebot in bestimmten Ländern dennoch dem Glücksspielrecht unterliegt, auch wenn keine Auszahlungen von Echtgeld erfolgen. Dies kann zu regulatorischen Hürden oder Einschränkungen bei der Vermarktung führen. Es empfiehlt sich, auch auf reputativer Ebene Vorsicht walten zu lassen. Das Modell könnte von Teilen der Öffentlichkeit oder den Medien als irreführend oder potenziell ausbeuterisch wahrgenommen werden, insbesondere wenn hohe In-App-Ausgaben ohne reale Gegenleistung entstehen. +Ein weiteres Risiko betrifft die Marktakzeptanz. Da keine echten Gewinne erzielt werden können, besteht die Gefahr, dass potenzielle Nutzer dem Angebot mit geringem Interesse begegnen oder es schnell wieder verlassen. Die Kaufschwelle ist in diesem Zusammenhang von großer Bedeutung. Ohne die Aussicht auf Rückgewinn oder reale Belohnung wäre die Bereitschaft, für virtuelle Inhalte Geld auszugeben, deutlich geringer als bei klassischen Echtgeld-Casinos. + +Aus den genannten Chancen und Risiken ergeben sich klare Anforderungen für das Projekt. Es ist von besonderer Wichtigkeit, dass die Spielbedingungen stets transparent dargestellt werden und die Nutzer darüber informiert werden, dass keine Auszahlungen von Echtgeld erfolgen. Um rechtliche Schwierigkeiten zu vermeiden, ist zudem eine umfassende rechtliche Prüfung in allen relevanten Zielmärkten erforderlich. Auf echte Gewinnchancen muss verzichtet werden, dennoch ist das Nutzererlebnis attraktiv zu gestalten. Dies kann beispielsweise durch die Implementierung von Gamification-Elementen, ansprechendem visuellen Feedback und sozialen Features, welche die Unterhaltung fördern, realisiert werden. Gleichzeitig ist ein ethisches Branding erforderlich, das das Produkt deutlich von traditionellen Glücksspielanbietern abgrenzt. Schließlich sollten innovative Monetarisierungsmodelle zum Einsatz kommen, wie Abonnements, Mikrotransaktionen oder auch Spendenmechanismen, um wirtschaftlich erfolgreich zu sein. + + +\subsection{Marketing Mix} + +\subsubsection{Preispolitik} +Bei der Markteinführung ist eine Positionierung auf einem niedrigen bis mittleren Preisniveau vorgesehen. Das Ziel besteht darin, die Einstiegshürde möglichst gering zu halten und dadurch eine breite Zielgruppe zum Ausprobieren zu motivieren. Nutzer können bereits mit geringen Beträgen ab etwa 1 bis 5 Euro spielen. Zur Steigerung der Attraktivität tragen Willkommensboni, Freispiele oder die Verdopplung der ersten Einzahlung bei. Diese Maßnahmen positionieren das Produkt klar als unterhaltungsorientiertes, risikoarmes Angebot, das sich bewusst von klassischen Echtgeld-Casinos mit hoher finanzieller Schwelle abgrenzt. + +Im Laufe des Produktlebenszyklus ist eine flexible Anpassung des Preismodells vorgesehen. In der Einführungsphase werden Niedrigpreise und Rabatte eingesetzt, um Nutzer zu gewinnen. In der Wachstumsphase besteht die Möglichkeit, größere Pakete einzuführen, wie beispielsweise Monatsabonnements oder Coin-Bundles. Während der Reifephase rücken Maßnahmen wie das Up-Selling in den Fokus, etwa durch den Verkauf von kosmetischen Items, Sonderfunktionen oder VIP-Zugängen. In der Sättigungsphase dienen zeitlich begrenzte Rabatte, Sonderaktionen und Events der Reaktivierung inaktiver Nutzer. Preisänderungen sollen stets mit neuen Inhalten, Features oder Events kombiniert werden, um die Zahlungsbereitschaft zu steigern und zu legitimieren. + +Als zentrale Preisstrategie kommt eine Kombination aus Promotions- und Bonusstrategien zum Einsatz. Hierzu zählen insbesondere Treueprämien und VIP-Stufenprogramme sowie zeitlich befristete Sonderangebote, wie etwa Freispiele am Wochenende. Die vorliegende Strategie zielt darauf ab, kurzfristig eine große Anzahl an Nutzern zu gewinnen, diese emotional an das Produkt zu binden und sie zur wiederholten Einzahlung zu motivieren, obwohl eine Auszahlung in Echtgeld nicht möglich ist. + +Darüber hinaus sind Preisdifferenzierungen ein zentraler Bestandteil der Monetarisierung. Es ist eine segmentbasierte Differenzierung geplant, bei der Gelegenheitsspieler kleinere Pakete erhalten, während Vielspieler Zugang zu größeren, exklusiveren Angeboten haben. Darüber hinaus wäre auch eine zeitliche Preisdifferenzierung, wie etwa Rabatte zu bestimmten Tageszeiten oder saisonalen Events, vorstellbar. Ergänzend werden psychologische Preisstrategien eingesetzt, wie etwa die Preisgestaltung mit Schwellenwerten (z. B. 4,99 € statt 5,00 €), um die Zahlungsbereitschaft der Kunden auf subtilere Weise zu fördern. + +Die Preisgestaltung verfolgt insgesamt eine wachstumsorientierte Strategie, die Nutzer sowohl in der frühen als auch in der langfristigen Nutzung des Produkts optimal anspricht und gleichzeitig wirtschaftlich tragfähig bleibt. + +\subsubsection{Kommunikationspolitik} +Das Produkt soll mittels einer strategisch ausgerichteten Werbestrategie, die verschiedene Kommunikationskanäle einbezieht, bekannt gemacht werden. Im Mittelpunkt stehen dabei Social-Media-Plattformen wie Meta (Facebook), TikTok und Instagram. Dort besteht die Möglichkeit, visuell ansprechende Kurzvideos zu veröffentlichen, die Spielspaß, schnelle Action und Belohnungssysteme in den Vordergrund stellen. Des Weiteren empfiehlt sich die Durchführung von Influencer-Marketing, um eine authentische Reichweite und ein Vertrauenverhältnis aufzubauen. Darüber hinaus erweisen sich Mobile Ads, also In-App-Werbung in anderen Spielen oder Anwendungen, als besonders effektiv, da sie spielaffine und potenziell zahlungsbereite Nutzer direkt im Nutzungskontext erreichen. + +Ein weiterer wichtiger Kanal ist das Suchmaschinenmarketing, insbesondere über Google Ads. In diesem Kontext ist die Implementierung zielgerichteter Kampagnen mit spezifischen Keywords wie "kostenlos spielen", "Online Slots", "Casino ohne Auszahlung" oder "Spielspaß mit Echtgeld" ratsam. Dabei ist es essenziell, den Nutzer stets einen eindeutigen Hinweis zu präsentieren, dass die Erzielung von Echtgeldgewinnen nicht möglich ist. Darüber hinaus besteht die Möglichkeit, Streaming-Plattformen wie Twitch und YouTube zu nutzen, indem mit Streamern kooperiert wird, die unterhaltsames Gameplay zeigen und dabei nicht den Gewinn, sondern das Spielerlebnis betonen. + +In allen Kanälen ist eine klare und transparente Kommunikation von essentieller Bedeutung, in welcher darauf hingewiesen wird, dass keine Auszahlungsfunktion zur Verfügung steht. Die Werbung muss das Produkt eindeutig als Entertainment-Angebot und nicht als Glücksspiel mit Gewinnerwartung positionieren, um sowohl die Nutzererwartung korrekt zu steuern als auch rechtlichen Risiken vorzubeugen. + +Die Werbemaßnahmen sollen darauf abzielen, zentrale Produkteigenschaften und Mehrwerte zu kommunizieren, die das geplante Unterhaltungs-Casino von der Konkurrenz abheben. Im Mittelpunkt steht dabei die technische und visuelle Qualität des Angebots, wie schnelle Ladezeiten, eine intuitive Bedienoberfläche sowie hochwertige Grafiken und flüssige Animationen sollen ein modernes und ansprechendes Nutzererlebnis vermitteln. Gleichzeitig wird großer Wert auf Transparenz gelegt, insbesondere durch klare Hinweise auf den Unterhaltungscharakter des Spiels, etwa durch Formulierungen wie "Kein echtes Glücksspiel – reines Entertainment". + +Darüber hinaus ist es von entscheidender Bedeutung, die niedrigen Einstiegshürden deutlich zu kommunizieren. Durch die Bereitstellung von Willkommensangeboten, geringen Einstiegskosten und einer einfachen Einzahlung über moderne Zahlungsschnittstellen wird die Hemmschwelle für neue Nutzer gesenkt. Im Bereich der Sicherheit wird durch die Implementierung von OAuth2 auf aktuelle Standards gesetzt, was ebenfalls als vertrauensbildendes Element in der Werbung hervorgehoben werden kann. Die Intention der kommunizierten Informationen ist es, das Produkt als leicht zugänglich, sicher, unterhaltsam und fair zu positionieren. + +\subsubsection{Distributionspolitik} +Das Produkt wird als moderne Webanwendung direkt über den Browser bereitgestellt und ist somit ohne Download oder Installation für den Nutzer zugänglich. Um das Online-Casino nutzen zu können, sind einige technische Voraussetzungen erforderlich. Für die Anwendung wird ein aktueller Webbrowser wie Google Chrome, Mozilla Firefox, Safari oder Microsoft Edge vorausgesetzt. Zudem ist JavaScript aktiviert sein, da dieses für die Darstellung der Spielmechanik und der Benutzeroberfläche essenziell ist. Für eine reibungslose Kommunikation mit dem Server und ein flüssiges Spielerlebnis ist eine stabile Internetverbindung erforderlich. Zusätzlich ist die Erlaubnis von Cookies erforderlich, um Sitzungsdaten zu speichern und personalisierte Inhalte oder Nutzertracking zu ermöglichen. + +Um ein optimales Spielerlebnis zu gewährleisten, wird die Nutzung auf einem Desktop-Computer oder Tablet mit ausreichend großem Bildschirm empfohlen, da dies die Übersichtlichkeit und Bedienbarkeit deutlich verbessert. Die Nutzung ist insbesondere dann stabil und sicher, wenn das Betriebssystem aktualisiert ist. Als aktuelle Versionen kommen in diesem Fall Windows 10 oder höher, macOS ab Version 11 sowie die jeweils aktuellen Versionen von Android oder iOS in Betracht. Die Webanwendung zeichnet sich insgesamt durch ihre Flexibilität und Plattformunabhängigkeit aus und ist mit gängigen Geräten und Standards problemlos kompatibel. + + +\subsection{Wirtschaftlichkeitsbetrachtung} +Die Wirtschaftlichkeit des geplanten Produkts ist grundsätzlich als sehr hoch einzuschätzen, sofern zentrale Rahmenbedingungen erfüllt sind. Ein entscheidender Vorteil besteht darin, dass sämtliche Einzahlungen als Einnahmen verbucht werden können, da es keine Auszahlungsverpflichtungen gibt. Dies ist ein klarer Unterschied zu klassischen Online-Casinos. Gleichzeitig fallen geringe variable Kosten pro Nutzer an, da weder physische Produkte hergestellt noch verschickt werden müssen und auch keine echten Geldgewinne ausbezahlt werden. Das Geschäftsmodell ist darüber hinaus hochgradig skalierbar. Ist die Plattform erst einmal technisch stabil aufgebaut, kann sie mit nur geringem zusätzlichem Aufwand von tausenden oder sogar Millionen Nutzern gleichzeitig genutzt werden. + +Die möglichen Einnahmequellen sind vielfältig. Neben den klassischen Echtgeld-Einzahlungen bieten sich zusätzliche Monetarisierungswege an, etwa durch In-App-Verkäufe, wie kosmetische Upgrades oder VIP-Status, durch Werbung innerhalb der Plattform oder über Affiliate-Kooperationen mit passenden Partnern. Diese Faktoren tragen dazu bei, dass das Modell aus wirtschaftlicher Sicht äußerst attraktiv erscheint. + +Die Rentabilität ist jedoch von mehreren kritischen Erfolgsfaktoren abhängig. Zu den erforderlichen Maßnahmen zählen die rechtliche Absicherung in den Zielmärkten, eine ausreichend hohe Nutzerakzeptanz trotz fehlender Auszahlungsfunktion und insbesondere die Fähigkeit, eine langfristige Kundenbindung durch ein unterhaltsames, faires und transparentes Spielerlebnis sicherzustellen. + +Dennoch sind bei derartigen Unternehmungen stets die bestehenden Risiken zu berücksichtigen. Auf rechtlicher Ebene besteht die Gefahr, dass das Angebot mit echtem Glücksspiel verwechselt wird. Sollte der Eindruck entstehen, dass Nutzer reale Gewinne erwarten dürfen, so könnte dies rechtlich als Täuschung gewertet werden. Dies könnte mit strafrechtlichen Konsequenzen verbunden sein. Zudem existieren international unterschiedliche Regulierungen im Glücksspielrecht, die auch sogenannte Social Casinos betreffen können, etwa durch Lizenzpflichten oder strenge Anforderungen an die AGB und die Nutzeraufklärung. + +Auch auf Ebene der Markenreputation können Risiken entstehen. Sollten Nutzer erst im Nachhinein erkennen, dass keine Auszahlung möglich ist, könnten sie sich getäuscht fühlen. Dies könnte sich in Form von negativen Bewertungen, Beschwerden oder sogar Shitstorms auf Social Media und Plattformen wie Trustpilot äußern. + +Zudem ist der Markt von hoher Wettbewerbsintensität geprägt, in dem sowohl etablierte Online-Casinos als auch renommierte Anbieter von Social Games bereits eine starke Marktposition innehaben. Die Monetarisierung eines Produkts kann nur dann erfolgreich sein, wenn es gelingt, die Nutzer trotz fehlender Gewinnaussicht emotional an das Produkt zu binden und sie zum freiwilligen Erwerb virtueller Inhalte zu motivieren. An dieser Stelle wird sich letztlich entscheiden, ob das vorhandene wirtschaftliche Potenzial auch realisiert werden kann. diff --git a/projektdokumentation/LICENSE.md b/projektdokumentation/LICENSE.md new file mode 100644 index 0000000..6e73c99 --- /dev/null +++ b/projektdokumentation/LICENSE.md @@ -0,0 +1,98 @@ +[![Creative Commons Lizenzvertrag](https://i.creativecommons.org/l/by-sa/4.0/88x31.png)](http://creativecommons.org/licenses/by-sa/4.0/) +LaTeX-Vorlage zur IHK-Projektdokumentation für Fachinformatiker Anwendungsentwicklung von [Stefan Macke](http://fiae.link/LaTeXVorlageFIAE) ist lizenziert unter einer [Creative Commons Namensnennung - Weitergabe unter gleichen Bedingungen 4.0 International Lizenz](http://creativecommons.org/licenses/by-sa/4.0/). + +### _Lizenz_ + +DER GEGENSTAND DIESER LIZENZ (WIE UNTER "SCHUTZGEGENSTAND" DEFINIERT) WIRD UNTER DEN BEDINGUNGEN DIESER CREATIVE COMMONS PUBLIC LICENSE ("CCPL", "LIZENZ" ODER "LIZENZVERTRAG") ZUR VERFÜGUNG GESTELLT. DER SCHUTZGEGENSTAND IST DURCH DAS URHEBERRECHT UND/ODER ANDERE GESETZE GESCHÜTZT. JEDE FORM DER NUTZUNG DES SCHUTZGEGENSTANDES, DIE NICHT AUFGRUND DIESER LIZENZ ODER DURCH GESETZE GESTATTET IST, IST UNZULÄSSIG. + +DURCH DIE AUSÜBUNG EINES DURCH DIESE LIZENZ GEWÄHRTEN RECHTS AN DEM SCHUTZGEGENSTAND ERKLÄREN SIE SICH MIT DEN LIZENZBEDINGUNGEN RECHTSVERBINDLICH EINVERSTANDEN. SOWEIT DIESE LIZENZ ALS LIZENZVERTRAG ANZUSEHEN IST, GEWÄHRT IHNEN DER LIZENZGEBER DIE IN DER LIZENZ GENANNTEN RECHTE UNENTGELTLICH UND IM AUSTAUSCH DAFÜR, DASS SIE DAS GEBUNDENSEIN AN DIE LIZENZBEDINGUNGEN AKZEPTIEREN. + +**1\. Definitionen** + +1. Der Begriff "**Abwandlung**" im Sinne dieser Lizenz bezeichnet das Ergebnis jeglicher Art von Veränderung des Schutzgegenstandes, solange die eigenpersönlichen Züge des Schutzgegenstandes darin nicht verblassen und daran eigene Schutzrechte entstehen. Das kann insbesondere eine Bearbeitung, Umgestaltung, Änderung, Anpassung, Übersetzung oder Heranziehung des Schutzgegenstandes zur Vertonung von Laufbildern sein. Nicht als Abwandlung des Schutzgegenstandes gelten seine Aufnahme in eine Sammlung oder ein Sammelwerk und die freie Benutzung des Schutzgegenstandes. +2. Der Begriff "**Sammelwerk**" im Sinne dieser Lizenz meint eine Zusammenstellung von literarischen, künstlerischen oder wissenschaftlichen Inhalten, sofern diese Zusammenstellung aufgrund von Auswahl und Anordnung der darin enthaltenen selbständigen Elemente eine geistige Schöpfung darstellt, unabhängig davon, ob die Elemente systematisch oder methodisch angelegt und dadurch einzeln zugänglich sind oder nicht. +3. "**Verbreiten**" im Sinne dieser Lizenz bedeutet, den Schutzgegenstand oder Abwandlungen im Original oder in Form von Vervielfältigungsstücken, mithin in körperlich fixierter Form der Öffentlichkeit anzubieten oder in Verkehr zu bringen. +4. Unter "**Lizenzelementen**" werden im Sinne dieser Lizenz die folgenden übergeordneten Lizenzcharakteristika verstanden, die vom Lizenzgeber ausgewählt wurden und in der Bezeichnung der Lizenz zum Ausdruck kommen: "Namensnennung", "Weitergabe unter gleichen Bedingungen". +5. Der "**Lizenzgeber**" im Sinne dieser Lizenz ist diejenige natürliche oder juristische Person oder Gruppe, die den Schutzgegenstand unter den Bedingungen dieser Lizenz anbietet und insoweit als Rechteinhaberin auftritt. +6. "**Rechteinhaber**" im Sinne dieser Lizenz ist der Urheber des Schutzgegenstandes oder jede andere natürliche oder juristische Person oder Gruppe von Personen, die am Schutzgegenstand ein Immaterialgüterrecht erlangt hat, welches die in Abschnitt 3 genannten Handlungen erfasst und bei dem eine Einräumung von Nutzungsrechten oder eine Weiterübertragung an Dritte möglich ist. +7. Der Begriff "**Schutzgegenstand**" bezeichnet in dieser Lizenz den literarischen, künstlerischen oder wissenschaftlichen Inhalt, der unter den Bedingungen dieser Lizenz angeboten wird. Das kann insbesondere eine persönliche geistige Schöpfung jeglicher Art, ein Werk der kleinen Münze, ein nachgelassenes Werk oder auch ein Lichtbild oder anderes Objekt eines verwandten Schutzrechts sein, unabhängig von der Art seiner Fixierung und unabhängig davon, auf welche Weise jeweils eine Wahrnehmung erfolgen kann, gleichviel ob in analoger oder digitaler Form. Soweit Datenbanken oder Zusammenstellungen von Daten einen immaterialgüterrechtlichen Schutz eigener Art genießen, unterfallen auch sie dem Begriff "Schutzgegenstand" im Sinne dieser Lizenz. +8. Mit "**Sie**" bzw. "**Ihnen**" ist die natürliche oder juristische Person gemeint, die in dieser Lizenz im Abschnitt 3 genannte Nutzungen des Schutzgegenstandes vornimmt und zuvor in Hinblick auf den Schutzgegenstand nicht gegen Bedingungen dieser Lizenz verstoßen oder aber die ausdrückliche Erlaubnis des Lizenzgebers erhalten hat, die durch diese Lizenz gewährten Nutzungsrechte trotz eines vorherigen Verstoßes auszuüben. +9. Unter "**Öffentlich Zeigen**" im Sinne dieser Lizenz sind Veröffentlichungen und Präsentationen des Schutzgegenstandes zu verstehen, die für eine Mehrzahl von Mitgliedern der Öffentlichkeit bestimmt sind und in unkörperlicher Form mittels öffentlicher Wiedergabe in Form von Vortrag, Aufführung, Vorführung, Darbietung, Sendung, Weitersendung, zeit- und ortsunabhängiger Zugänglichmachung oder in körperlicher Form mittels Ausstellung erfolgen, unabhängig von bestimmten Veranstaltungen und unabhängig von den zum Einsatz kommenden Techniken und Verfahren, einschließlich drahtgebundener oder drahtloser Mittel und Einstellen in das Internet. +10. "**Vervielfältigen**" im Sinne dieser Lizenz bedeutet, mittels beliebiger Verfahren Vervielfältigungsstücke des Schutzgegenstandes herzustellen, insbesondere durch Ton- oder Bildaufzeichnungen, und umfasst auch den Vorgang, erstmals körperliche Fixierungen des Schutzgegenstandes sowie Vervielfältigungsstücke dieser Fixierungen anzufertigen, sowie die Übertragung des Schutzgegenstandes auf einen Bild- oder Tonträger oder auf ein anderes elektronisches Medium, gleichviel ob in digitaler oder analoger Form. +11. "**Mit Creative Commons kompatible Lizenz**" bezeichnet eine Lizenz, die unter https://creativecommons.org/compatiblelicenses aufgelistet ist und die durch Creative Commons als grundsätzlich zur vorliegenden Lizenz äquivalent akzeptiert wurde, da zumindest folgende Voraussetzungen erfüllt sind: + + Diese mit Creative Commons kompatible Lizenz + + 1. enthält Bestimmungen, welche die gleichen Ziele verfolgen, die gleiche Bedeutung haben und die gleichen Wirkungen erzeugen wie die Lizenzelemente der vorliegenden Lizenz; und + 2. erlaubt ausdrücklich das Lizenzieren von ihr unterstellten Abwandlungen unter vorliegender Lizenz, unter einer anderen rechtsordnungsspezifisch angepassten Creative-Commons-Lizenz mit denselben Lizenzelementen, wie sie die vorliegende Lizenz aufweist, oder unter der entsprechenden Creative-Commons-Unported-Lizenz. + +**2\. Schranken des Immaterialgüterrechts** + +Diese Lizenz ist in keiner Weise darauf gerichtet, Befugnisse zur Nutzung des Schutzgegenstandes zu vermindern, zu beschränken oder zu vereiteln, die Ihnen aufgrund der Schranken des Urheberrechts oder anderer Rechtsnormen bereits ohne Weiteres zustehen oder sich aus dem Fehlen eines immaterialgüterrechtlichen Schutzes ergeben. + +**3\. Einräumung von Nutzungsrechten** + +Unter den Bedingungen dieser Lizenz räumt Ihnen der Lizenzgeber - unbeschadet unverzichtbarer Rechte und vorbehaltlich des Abschnitts 3.e) - das vergütungsfreie, räumlich und zeitlich (für die Dauer des Schutzrechts am Schutzgegenstand) unbeschränkte einfache Recht ein, den Schutzgegenstand auf die folgenden Arten und Weisen zu nutzen ("unentgeltlich eingeräumtes einfaches Nutzungsrecht für jedermann"): + +1. Den Schutzgegenstand in beliebiger Form und Menge zu vervielfältigen, ihn in Sammelwerke zu integrieren und ihn als Teil solcher Sammelwerke zu vervielfältigen; +2. Abwandlungen des Schutzgegenstandes anzufertigen, einschließlich Übersetzungen unter Nutzung jedweder Medien, sofern deutlich erkennbar gemacht wird, dass es sich um Abwandlungen handelt; +3. den Schutzgegenstand, allein oder in Sammelwerke aufgenommen, öffentlich zu zeigen und zu verbreiten; +4. Abwandlungen des Schutzgegenstandes zu veröffentlichen, öffentlich zu zeigen und zu verbreiten. +5. Bezüglich Vergütung für die Nutzung des Schutzgegenstandes gilt Folgendes: + + 1. **Unverzichtbare gesetzliche Vergütungsansprüche**: Soweit unverzichtbare Vergütungsansprüche im Gegenzug für gesetzliche Lizenzen vorgesehen oder Pauschalabgabensysteme (zum Beispiel für Leermedien) vorhanden sind, behält sich der Lizenzgeber das ausschließliche Recht vor, die entsprechende Vergütung einzuziehen für jede Ausübung eines Rechts aus dieser Lizenz durch Sie. + 2. **Vergütung bei Zwangslizenzen**: Sofern Zwangslizenzen außerhalb dieser Lizenz vorgesehen sind und zustande kommen, verzichtet der Lizenzgeber für alle Fälle einer lizenzgerechten Nutzung des Schutzgegenstandes durch Sie auf jegliche Vergütung. + 3. **Vergütung in sonstigen Fällen**: Bezüglich lizenzgerechter Nutzung des Schutzgegenstandes durch Sie, die nicht unter die beiden vorherigen Abschnitte (i) und (ii) fällt, verzichtet der Lizenzgeber auf jegliche Vergütung, unabhängig davon, ob eine Einziehung der Vergütung durch ihn selbst oder nur durch eine Verwertungsgesellschaft möglich wäre. + +Das vorgenannte Nutzungsrecht wird für alle bekannten sowie für alle noch nicht bekannten Nutzungsarten eingeräumt. Es beinhaltet auch das Recht, solche Änderungen am Schutzgegenstand vorzunehmen, die für bestimmte nach dieser Lizenz zulässige Nutzungen technisch erforderlich sind. Alle sonstigen Rechte, die über diesen Abschnitt hinaus nicht ausdrücklich durch den Lizenzgeber eingeräumt werden, bleiben diesem allein vorbehalten. Soweit Datenbanken oder Zusammenstellungen von Daten Schutzgegenstand dieser Lizenz oder Teil dessen sind und einen immaterialgüterrechtlichen Schutz eigener Art genießen, verzichtet der Lizenzgeber auf sämtliche aus diesem Schutz resultierenden Rechte. + +**4\. Bedingungen** + +Die Einräumung des Nutzungsrechts gemäß Abschnitt 3 dieser Lizenz erfolgt ausdrücklich nur unter den folgenden Bedingungen: + +1. Sie dürfen den Schutzgegenstand ausschließlich unter den Bedingungen dieser Lizenz verbreiten oder öffentlich zeigen. Sie müssen dabei stets eine Kopie dieser Lizenz oder deren vollständige Internetadresse in Form des Uniform-Resource-Identifier (URI) beifügen. Sie dürfen keine Vertrags- oder Nutzungsbedingungen anbieten oder fordern, die die Bedingungen dieser Lizenz oder die durch diese Lizenz gewährten Rechte beschränken. Sie dürfen den Schutzgegenstand nicht unterlizenzieren. Bei jeder Kopie des Schutzgegenstandes, die Sie verbreiten oder öffentlich zeigen, müssen Sie alle Hinweise unverändert lassen, die auf diese Lizenz und den Haftungsausschluss hinweisen. Wenn Sie den Schutzgegenstand verbreiten oder öffentlich zeigen, dürfen Sie (in Bezug auf den Schutzgegenstand) keine technischen Maßnahmen ergreifen, die den Nutzer des Schutzgegenstandes in der Ausübung der ihm durch diese Lizenz gewährten Rechte behindern können. Dieser Abschnitt 4.a) gilt auch für den Fall, dass der Schutzgegenstand einen Bestandteil eines Sammelwerkes bildet, was jedoch nicht bedeutet, dass das Sammelwerk insgesamt dieser Lizenz unterstellt werden muss. Sofern Sie ein Sammelwerk erstellen, müssen Sie auf die Mitteilung eines Lizenzgebers hin aus dem Sammelwerk die in Abschnitt 4.c) aufgezählten Hinweise entfernen. Wenn Sie eine Abwandlung vornehmen, müssen Sie auf die Mitteilung eines Lizenzgebers hin von der Abwandlung die in Abschnitt 4.c) aufgezählten Hinweise entfernen. +2. Sie dürfen eine Abwandlung ausschließlich unter den Bedingungen + + 1. dieser Lizenz, + 2. einer späteren Version dieser Lizenz mit denselben Lizenzelementen, + 3. einer rechtsordnungsspezifischen Creative-Commons-Lizenz mit denselben Lizenzelementen ab Version 3.0 aufwärts (z.B. Namensnennung - Weitergabe unter gleichen Bedingungen 3.0 US), + 4. der Creative-Commons-Unported-Lizenz mit denselben Lizenzelementen ab Version 3.0 aufwärts, oder + 5. einer mit Creative Commons kompatiblen Lizenz + verbreiten oder öffentlich zeigen. + + Falls Sie die Abwandlung gemäß Abschnitt (v) unter einer mit Creative Commons kompatiblen Lizenz lizenzieren, müssen Sie deren Lizenzbestimmungen Folge leisten. + + Falls Sie die Abwandlungen unter einer der unter (i)-(iv) genannten Lizenzen ("Verwendbare Lizenzen") lizenzieren, müssen Sie deren Lizenzbestimmungen sowie folgenden Bestimmungen Folge leisten: Sie müssen stets eine Kopie der verwendbaren Lizenz oder deren vollständige Internetadresse in Form des Uniform-Resource-Identifier (URI) beifügen, wenn Sie die Abwandlung verbreiten oder öffentlich zeigen. Sie dürfen keine Vertrags- oder Nutzungsbedingungen anbieten oder fordern, die die Bedingungen der verwendbaren Lizenz oder die durch sie gewährten Rechte beschränken. Bei jeder Abwandlung, die Sie verbreiten oder öffentlich zeigen, müssen Sie alle Hinweise auf die verwendbare Lizenz und den Haftungsausschluss unverändert lassen. Wenn Sie die Abwandlung verbreiten oder öffentlich zeigen, dürfen Sie (in Bezug auf die Abwandlung) keine technischen Maßnahmen ergreifen, die den Nutzer der Abwandlung in der Ausübung der ihm durch die verwendbare Lizenz gewährten Rechte behindern können. Dieser Abschnitt 4.b) gilt auch für den Fall, dass die Abwandlung einen Bestandteil eines Sammelwerkes bildet, was jedoch nicht bedeutet, dass das Sammelwerk insgesamt der verwendbaren Lizenz unterstellt werden muss. + +3. Die Verbreitung und das öffentliche Zeigen des Schutzgegenstandes oder auf ihm aufbauender Abwandlungen oder ihn enthaltender Sammelwerke ist Ihnen nur unter der Bedingung gestattet, dass Sie, vorbehaltlich etwaiger Mitteilungen im Sinne von Abschnitt 4.a), alle dazu gehörenden Rechtevermerke unberührt lassen. Sie sind verpflichtet, die Rechteinhaberschaft in einer der Nutzung entsprechenden, angemessenen Form anzuerkennen, indem Sie - soweit bekannt - Folgendes angeben: + + 1. Den Namen (oder das Pseudonym, falls ein solches verwendet wird) des Rechteinhabers und / oder, falls der Lizenzgeber im Rechtevermerk, in den Nutzungsbedingungen oder auf andere angemessene Weise eine Zuschreibung an Dritte vorgenommen hat (z.B. an eine Stiftung, ein Verlagshaus oder eine Zeitung) ("Zuschreibungsempfänger"), Namen bzw. Bezeichnung dieses oder dieser Dritten; + 2. den Titel des Inhaltes; + 3. in einer praktikablen Form den Uniform-Resource-Identifier (URI, z.B. Internetadresse), den der Lizenzgeber zum Schutzgegenstand angegeben hat, es sei denn, dieser URI verweist nicht auf den Rechtevermerk oder die Lizenzinformationen zum Schutzgegenstand; + 4. und im Falle einer Abwandlung des Schutzgegenstandes in Übereinstimmung mit Abschnitt 3.b) einen Hinweis darauf, dass es sich um eine Abwandlung handelt. + Die nach diesem Abschnitt 4.c) erforderlichen Angaben können in jeder angemessenen Form gemacht werden; im Falle einer Abwandlung des Schutzgegenstandes oder eines Sammelwerkes müssen diese Angaben das Minimum darstellen und bei gemeinsamer Nennung mehrerer Rechteinhaber dergestalt erfolgen, dass sie zumindest ebenso hervorgehoben sind wie die Hinweise auf die übrigen Rechteinhaber. Die Angaben nach diesem Abschnitt dürfen Sie ausschließlich zur Angabe der Rechteinhaberschaft in der oben bezeichneten Weise verwenden. Durch die Ausübung Ihrer Rechte aus dieser Lizenz dürfen Sie ohne eine vorherige, separat und schriftlich vorliegende Zustimmung des Lizenzgebers und / oder des Zuschreibungsempfängers weder explizit noch implizit irgendeine Verbindung zum Lizenzgeber oder Zuschreibungsempfänger und ebenso wenig eine Unterstützung oder Billigung durch ihn andeuten. + +4. Die oben unter 4.a) bis c) genannten Einschränkungen gelten nicht für solche Teile des Schutzgegenstandes, die allein deshalb unter den Schutzgegenstandsbegriff fallen, weil sie als Datenbanken oder Zusammenstellungen von Daten einen immaterialgüterrechtlichen Schutz eigener Art genießen. +5. Persönlichkeitsrechte bleiben - soweit sie bestehen - von dieser Lizenz unberührt. + +**5\. Gewährleistung** + +SOFERN KEINE ANDERS LAUTENDE, SCHRIFTLICHE VEREINBARUNG ZWISCHEN DEM LIZENZGEBER UND IHNEN GESCHLOSSEN WURDE UND SOWEIT MÄNGEL NICHT ARGLISTIG VERSCHWIEGEN WURDEN, BIETET DER LIZENZGEBER DEN SCHUTZGEGENSTAND UND DIE EINRÄUMUNG VON RECHTEN UNTER AUSSCHLUSS JEGLICHER GEWÄHRLEISTUNG AN UND ÜBERNIMMT WEDER AUSDRÜCKLICH NOCH KONKLUDENT GARANTIEN IRGENDEINER ART. DIES UMFASST INSBESONDERE DAS FREISEIN VON SACH- UND RECHTSMÄNGELN, UNABHÄNGIG VON DEREN ERKENNBARKEIT FÜR DEN LIZENZGEBER, DIE VERKEHRSFÄHIGKEIT DES SCHUTZGEGENSTANDES, SEINE VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK SOWIE DIE KORREKTHEIT VON BESCHREIBUNGEN. DIESE GEWÄHRLEISTUNGSBESCHRÄNKUNG GILT NICHT, SOWEIT MÄNGEL ZU SCHÄDEN DER IN ABSCHNITT 6 BEZEICHNETEN ART FÜHREN UND AUF SEITEN DES LIZENZGEBERS DAS JEWEILS GENANNTE VERSCHULDEN BZW. VERTRETENMÜSSEN EBENFALLS VORLIEGT. + +**6\. Haftungsbeschränkung** + +DER LIZENZGEBER HAFTET IHNEN GEGENÜBER IN BEZUG AUF SCHÄDEN AUS DER VERLETZUNG DES LEBENS, DES KÖRPERS ODER DER GESUNDHEIT NUR, SOFERN IHM WENIGSTENS FAHRLÄSSIGKEIT VORZUWERFEN IST, FÜR SONSTIGE SCHÄDEN NUR BEI GROBER FAHRLÄSSIGKEIT ODER VORSATZ, UND ÜBERNIMMT DARÜBER HINAUS KEINERLEI FREIWILLIGE HAFTUNG. + +**7\. Erlöschen** + +1. Diese Lizenz und die durch sie eingeräumten Nutzungsrechte erlöschen mit Wirkung für die Zukunft im Falle eines Verstoßes gegen die Lizenzbedingungen durch Sie, ohne dass es dazu der Kenntnis des Lizenzgebers vom Verstoß oder einer weiteren Handlung einer der Vertragsparteien bedarf. Mit natürlichen oder juristischen Personen, die Abwandlungen des Schutzgegenstandes oder diesen enthaltende Sammelwerke unter den Bedingungen dieser Lizenz von Ihnen erhalten haben, bestehen nachträglich entstandene Lizenzbeziehungen jedoch solange weiter, wie die genannten Personen sich ihrerseits an sämtliche Lizenzbedingungen halten. Darüber hinaus gelten die Ziffern 1, 2, 5, 6, 7, und 8 auch nach einem Erlöschen dieser Lizenz fort. +2. Vorbehaltlich der oben genannten Bedingungen gilt diese Lizenz unbefristet bis der rechtliche Schutz für den Schutzgegenstand ausläuft. Davon abgesehen behält der Lizenzgeber das Recht, den Schutzgegenstand unter anderen Lizenzbedingungen anzubieten oder die eigene Weitergabe des Schutzgegenstandes jederzeit einzustellen, solange die Ausübung dieses Rechts nicht einer Kündigung oder einem Widerruf dieser Lizenz (oder irgendeiner Weiterlizenzierung, die auf Grundlage dieser Lizenz bereits erfolgt ist bzw. zukünftig noch erfolgen muss) dient und diese Lizenz unter Berücksichtigung der oben zum Erlöschen genannten Bedingungen vollumfänglich wirksam bleibt. + +**8\. Sonstige Bestimmungen** + +1. Jedes Mal wenn Sie den Schutzgegenstand für sich genommen oder als Teil eines Sammelwerkes verbreiten oder öffentlich zeigen, bietet der Lizenzgeber dem Empfänger eine Lizenz zu den gleichen Bedingungen und im gleichen Umfang an, wie Ihnen in Form dieser Lizenz. +2. Jedes Mal wenn Sie eine Abwandlung des Schutzgegenstandes verbreiten oder öffentlich zeigen, bietet der Lizenzgeber dem Empfänger eine Lizenz am ursprünglichen Schutzgegenstand zu den gleichen Bedingungen und im gleichen Umfang an, wie Ihnen in Form dieser Lizenz. +3. Sollte eine Bestimmung dieser Lizenz unwirksam sein, so bleibt davon die Wirksamkeit der Lizenz im Übrigen unberührt. +4. Keine Bestimmung dieser Lizenz soll als abbedungen und kein Verstoß gegen sie als zulässig gelten, solange die von dem Verzicht oder von dem Verstoß betroffene Seite nicht schriftlich zugestimmt hat. +5. Diese Lizenz (zusammen mit in ihr ausdrücklich vorgesehenen Erlaubnissen, Mitteilungen und Zustimmungen, soweit diese tatsächlich vorliegen) stellt die vollständige Vereinbarung zwischen dem Lizenzgeber und Ihnen in Bezug auf den Schutzgegenstand dar. Es bestehen keine Abreden, Vereinbarungen oder Erklärungen in Bezug auf den Schutzgegenstand, die in dieser Lizenz nicht genannt sind. Rechtsgeschäftliche Änderungen des Verhältnisses zwischen dem Lizenzgeber und Ihnen sind nur über Modifikationen dieser Lizenz möglich. Der Lizenzgeber ist an etwaige zusätzliche, einseitig durch Sie übermittelte Bestimmungen nicht gebunden. Diese Lizenz kann nur durch schriftliche Vereinbarung zwischen Ihnen und dem Lizenzgeber modifiziert werden. Derlei Modifikationen wirken ausschließlich zwischen dem Lizenzgeber und Ihnen und wirken sich nicht auf die Dritten gemäß Ziffern 8.a) und b) angeboteten Lizenzen aus. +6. Sofern zwischen Ihnen und dem Lizenzgeber keine anderweitige Vereinbarung getroffen wurde und soweit Wahlfreiheit besteht, findet auf diesen Lizenzvertrag das Recht der Bundesrepublik Deutschland Anwendung. \ No newline at end of file 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 diff --git a/projektdokumentation/Meta.tex b/projektdokumentation/Meta.tex new file mode 100644 index 0000000..0fc252c --- /dev/null +++ b/projektdokumentation/Meta.tex @@ -0,0 +1,14 @@ +% !TEX root = Projektdokumentation.tex + +% Hinweis: der Titel muss zum Inhalt des Projekts passen und den zentralen Inhalt des Projekts deutlich herausstellen +\newcommand{\titel}{Trustworthy Casino} +\newcommand{\untertitel}{Online-Spiele Casino} +\newcommand{\kompletterTitel}{\titel{} -- \untertitel} + +\newcommand{\autorName}{Phan Huy Tran} + +\newcommand{\betriebLogo}{hitec-logo.png} + +\newcommand{\ausbildungsberuf}{Fachinformatiker für Anwendungsentwicklung} +\newcommand{\betreff}{Dokumentation} +\newcommand{\projektZeitraum}{29.01.2025 - 12.06.2025} diff --git a/projektdokumentation/Projektdokumentation.tex b/projektdokumentation/Projektdokumentation.tex new file mode 100644 index 0000000..7bd2c8f --- /dev/null +++ b/projektdokumentation/Projektdokumentation.tex @@ -0,0 +1,63 @@ +\documentclass[ + 11pt, + ngerman, + toc=listof, % Abbildungsverzeichnis sowie Tabellenverzeichnis in das Inhaltsverzeichnis aufnehmen + toc=bibliography, % Literaturverzeichnis in das Inhaltsverzeichnis aufnehmen + footnotes=multiple, % Trennen von direkt aufeinander folgenden Fußnoten + parskip=half, % vertikalen Abstand zwischen Absätzen verwenden anstatt horizontale Einrückung von Folgeabsätzen + numbers=noendperiod % Den letzten Punkt nach einer Nummerierung entfernen (nach DIN 5008) +]{scrartcl} +\pdfminorversion=5 % erlaubt das Einfügen von pdf-Dateien bis Version 1.7, ohne eine Fehlermeldung zu werfen (keine Garantie für fehlerfreies Einbetten!) +\usepackage[utf8]{inputenc} % muss als erstes eingebunden werden, da Meta/Packages ggfs. Sonderzeichen enthalten + +\input{Meta} % Metadaten zu diesem Dokument (Autor usw.) +\input{Allgemein/Packages} % verwendete Packages +\input{Allgemein/Seitenstil} % Definitionen zum Aussehen der Seiten +\input{Allgemein/Befehle} % eigene allgemeine Befehle, die z.B. die Arbeit mit LaTeX erleichtern +\input{Befehle} % eigene projektspezifische Befehle, z.B. Abkürzungen usw. + +\begin{document} + +\phantomsection +\thispagestyle{plain} +\pdfbookmark[1]{Deckblatt}{deckblatt} +\input{Deckblatt} +\cleardoublepage + +% Preface -------------------------------------------------------------------- +\phantomsection +\pagenumbering{Roman} +\pdfbookmark[1]{Inhaltsverzeichnis}{inhalt} +\tableofcontents % Compile multiple times (e.g., 2-3 times) to ensure the Table of Contents is generated correctly. +\cleardoublepage + +\phantomsection +\listoffigures +\cleardoublepage + +\phantomsection +\cleardoublepage + +\phantomsection +\cleardoublepage + +\newcommand{\abkvz}{Abkürzungsverzeichnis} +\renewcommand{\nomname}{\abkvz} +\section*{\abkvz} +\markboth{\abkvz}{\abkvz} +\addcontentsline{toc}{section}{\abkvz} +\input{Abkuerzungen} +\clearpage + +% Inhalt --------------------------------------------------------------------- +\pagenumbering{arabic} +\input{Inhalt.tex} + +% Anhang --------------------------------------------------------------------- +\clearpage +\appendix +\pagenumbering{Roman} +\input{Anhang} + + +\end{document} diff --git a/projektdokumentation/README.md b/projektdokumentation/README.md new file mode 100644 index 0000000..ccf88be --- /dev/null +++ b/projektdokumentation/README.md @@ -0,0 +1,12 @@ +# LaTeX-Vorlage zur Projektdokumentation für Fachinformatiker Anwendungsentwicklung + +Die Vorlage umfasst neben dem Layout und den obligatorischen Elementen wie Deckblatt, Verzeichnissen und Literaturangaben insbesondere eine Vorstrukturierung der üblicherweise geforderten Inhalte einer Dokumentation zur betrieblichen Projektarbeit inklusive einiger Beispiele für wichtige Inhalte wie z.B. Kostenkalkulation und Amortisationsrechnung. Obwohl viele Inhalte der Vorlage speziell auf Anwendungsentwickler zugeschnitten sind, dürfte die Vorlage auch für die anderen IT-Berufe (Fachinformatiker Systemintegration, IT-Kaufleute usw.) interessant sein, da die Vorgaben hinsichtlich der Projektarbeit größtenteils übereinstimmen. + +Mehr Informationen und eine Beispieldokumentation auf Basis dieser Vorlage gibt es hier: [Vorlage für die Projektdokumentation][fiaevorlage]. + +[fiaevorlage]: http://fiae.link/LaTeXVorlageFIAE "Vorlage für die Projektdokumentation" + +# Lizenz + +[![Creative Commons Lizenzvertrag](https://i.creativecommons.org/l/by-sa/4.0/88x31.png)](http://creativecommons.org/licenses/by-sa/4.0/) +LaTeX-Vorlage zur IHK-Projektdokumentation für Fachinformatiker Anwendungsentwicklung von [Stefan Macke](http://fiae.link/LaTeXVorlageFIAE) ist lizenziert unter einer [Creative Commons Namensnennung - Weitergabe unter gleichen Bedingungen 4.0 International Lizenz](http://creativecommons.org/licenses/by-sa/4.0/). \ No newline at end of file diff --git a/projektdokumentation/Tabellen/Kostenaufstellung.tex b/projektdokumentation/Tabellen/Kostenaufstellung.tex new file mode 100644 index 0000000..d0e3307 --- /dev/null +++ b/projektdokumentation/Tabellen/Kostenaufstellung.tex @@ -0,0 +1,11 @@ +% Table generated by Excel2LaTeX from sheet 'Kostenaufstellung' +\begin{tabular}{lrlr} +\rowcolor{heading}\textbf{Vorgang} & \textbf{Zeit} & \textbf{Kosten pro Stunde} & \textbf{Kosten} \\ +Entwicklungskosten & 70 \mbox{h} & $\eur{10} + \eur{15} = \eur{25}$ & \eur{1750} \\ +\rowcolor{odd}Fachgespräch & 3 \mbox{h} & $\eur{25} + \eur{15} = \eur{40}$ & \eur{120} \\ +Abnahmetest & 1 \mbox{h} & $\eur{25} + \eur{15} = \eur{40}$ & \eur{40} \\ +\rowcolor{odd}Anwenderschulung & 25 \mbox{h} & $\eur{25} + \eur{15} = \eur{40}$ & \eur{1000} \\ +\hline +\hline +\rowcolor{heading}\textbf{} & \textbf{} & \textbf{} & \textbf{\eur{2910}} \\ +\end{tabular} diff --git a/projektdokumentation/Tabellen/Nutzwert.tex b/projektdokumentation/Tabellen/Nutzwert.tex new file mode 100644 index 0000000..0def9fd --- /dev/null +++ b/projektdokumentation/Tabellen/Nutzwert.tex @@ -0,0 +1,11 @@ +% Table generated by Excel2LaTeX from sheet 'Nutzwert' +\begin{tabular}{llllll} +\rowcolor{heading}\textbf{Eigenschaft} & \textbf{Gewichtung} & \textbf{Akelos} & \textbf{CakePHP} & \textbf{Symfony} & \textbf{Eigenentwicklung} \\ +\textbf{Dokumentation} & 5 & 4 & 3 & 5 & 0 \\ +\rowcolor{odd}\textbf{Reenginierung} & 3 & 4 & 2 & 5 & 3 \\ +\textbf{Generierung} & 3 & 5 & 5 & 5 & 2 \\ +\rowcolor{odd}\textbf{Testfälle} & 2 & 3 & 2 & 3 & 3 \\ +\textbf{Standardaufgaben} & 4 & 3 & 3 & 3 & 0 \\ +\rowcolor{heading}\textbf{Gesamt:} & \textbf{17} & \textbf{65} & \textbf{52} & \textbf{73} & \textbf{21} \\ +\rowcolor{odd}\textbf{Nutzwert:} & & \textbf{3,82} & \textbf{3,06} & \textbf{4,29} & \textbf{1,24}\\ +\end{tabular} diff --git a/projektdokumentation/Tabellen/Zeitersparnis.tex b/projektdokumentation/Tabellen/Zeitersparnis.tex new file mode 100644 index 0000000..552deb0 --- /dev/null +++ b/projektdokumentation/Tabellen/Zeitersparnis.tex @@ -0,0 +1,9 @@ + +\begin{tabular}{lllll} + \rowcolor{heading}\textbf{Vorgang} & \textbf{Anzahl pro Monat} & \textbf{Zeit alt} & \textbf{Zeit neu} & \textbf{Einsparung} \\ + Fehleranalyse & 10 & 30 min & 10 min & 200 min \\ + \rowcolor{odd}Einarbeitung Modulstruktur & 2 & 120 min & 30 min & 180 min \\ + Import von Daten & 5 & 10 min & 3 min & 35 min \\ + \hline + \rowcolor{heading}\textbf{Zeiteinsparung Gesamt pro Monat} & & & & \textbf{415 min} \\ +\end{tabular} \ No newline at end of file diff --git a/projektdokumentation/Tabellen/Zeitnachher.tex b/projektdokumentation/Tabellen/Zeitnachher.tex new file mode 100644 index 0000000..eb96845 --- /dev/null +++ b/projektdokumentation/Tabellen/Zeitnachher.tex @@ -0,0 +1,13 @@ +% Table generated by Excel2LaTeX from sheet 'Zeitnachher' +\begin{tabular}{lrrr} +\rowcolor{heading}\textbf{Phase} & \textbf{Geplant} & \textbf{Tatsächlich} & \textbf{Differenz} \\ +\textbf{Analysephase} & 6 h & 6 h & \\ +\rowcolor{odd}\textbf{Entwurfsphase} & 14 h & 14 h & \\ +\textbf{Implementierungsphase} & 37 h & 28 h & -9 h \\ +\rowcolor{odd}\textbf{Abnahmetest der Fachabteilung} & 5 h & 5 h & \\ +\textbf{Einführungsphase} & 1 h & 1 h & \\ +\rowcolor{odd}\textbf{Erstellen der Dokumentation} & 8 h & 17 h & +9 h \\ +\hline +\hline +\rowcolor{odd}\textbf{Gesamt} & 70 h & 70 h & \\ +\end{tabular} diff --git a/projektdokumentation/Tabellen/ZeitplanungKomplett.tex b/projektdokumentation/Tabellen/ZeitplanungKomplett.tex new file mode 100644 index 0000000..303e47c --- /dev/null +++ b/projektdokumentation/Tabellen/ZeitplanungKomplett.tex @@ -0,0 +1,48 @@ +% Table generated by Excel2LaTeX from sheet 'ZeitplanungKomplett' +\begin{tabularx}{\textwidth}{Xrrr} +\rowcolor{heading}\textbf{Analysephase} & \textbf{} & \textbf{} & \textbf{6 h} \\ +1. Analyse des Ist-Zustands & & 2 h & \\ +\rowcolor{odd}2. \gqq{Make or buy}-Entscheidung und Wirtschaftlichkeitsanalyse & & 1 h & \\ +3. Erstellen eines \gqq{Use-Case}-Diagramms & & 2 h & \\ +\rowcolor{odd}4. Erstellen des Lastenhefts & & 1 h & \\ + +\rowcolor{heading}\textbf{Entwurfsphase} & \textbf{} & \textbf{} & \textbf{14 h} \\ +1. UML-Komponentendiagramm erstellen & & 1 h & \\ +\rowcolor{odd}2. Datenbankentwurf & & 1 h & \\ +2.1. \ac{M2} Migration für Tabelle definieren & 1 h & & \\ +\rowcolor{odd}3. Erstellen des Schnittstellen Konzeptes für \ac{IX} & & 4 h & \\ +3.1. Erstellung Verbindung zur \ac{IX} \ac{API} & 1 h & & \\ +\rowcolor{odd}3.2. Verarbeitung der \ac{JSON}-Daten & 1 h & & \\ +3.3. Verarbeitung der Konfigurationsdatei der Anwendung & 2 h & & \\ +\rowcolor{odd}4. Benutzeroberflächen entwerfen und abstimmen & & 3 h & \\ +5. Erstellen des Schnittstellen Konzeptes für \ac{M2} & & 4 h & \\ +\rowcolor{odd}5.1 Erstellung Schnittstelle für \ac{M2} Import Funktionen & & 2 h & \\ +5.2 Erstellung Schnittstelle zur Verarbeitung der geholten \ac{IX} \ac{JSON}-Daten & & 2 h & \\ + +\rowcolor{heading}\textbf{Implementierungsphase} & \textbf{} & \textbf{} & \textbf{37 h} \\ +1. Anlegen der Datenbank & & 4 h & \\ +\rowcolor{odd}2. Einrichtung der Docker Umgebung zur lokalen Entwicklung & & 2 h & \\ +3. Programmierung der \ac{PHP}-Module für die Funktionen & & 29 h & \\ +\rowcolor{odd}3.1 Erstellung eines Client Moduls zur Abfrage der \ac{IX} \ac{API} per \ac{curl} Requests & 3 h & & \\ +3.2. Import der Produktdaten aus \ac{JSON}-Dateien & 2 h & & \\ +\rowcolor{odd}3.3. Import der Nutzerdaten aus \ac{JSON}-Dateien & 2 h & & \\ +3.4. Aufbereitung (Mapping) der geholten Daten für \ac{M2} & 3 h & & \\ +\rowcolor{odd}Einbindung der Konfigurationsdateien & 4 h & & \\ +3.6. Import der aufbereiteten Daten von \ac{IX} zu \ac{M2} & 5 h & & \\ +\rowcolor{odd}3.7. Einbindung der \ac{CRON}-Job Möglichkeit & 3 h & & \\ +3.8. Abdeckung der Module mit Unit-Tests & 4 h & & \\ +\rowcolor{odd}4. Umsetzung der \ac{CLI} Benutzeroberfläche & & 2 h & \\ + +\rowcolor{heading}\textbf{Abnahmetest der Fachabteilung} & \textbf{} & \textbf{} & \textbf{3 h} \\ +1. Abnahmetest der \ac{NSD} Mitarbeiter & & 3 h & \\ + +\rowcolor{heading}\textbf{Einführungsphase} & \textbf{} & \textbf{} & \textbf{1 h} \\ +1. Einführung/Benutzerschulung & & 1 h & \\ + +\rowcolor{heading}\textbf{Erstellen der Dokumentation} & \textbf{} & \textbf{} & \textbf{9 h} \\ +1. Erstellen der Entwicklerdokumentation & & 2 h & \\ + +\rowcolor{odd}2. Erstellen der Projektdokumentation & & 7 h & \\ + +\rowcolor{heading}\textbf{Gesamt} & \textbf{} & \textbf{} & \textbf{70 h} \\ +\end{tabularx} diff --git a/projektdokumentation/Tabellen/ZeitplanungKurz.tex b/projektdokumentation/Tabellen/ZeitplanungKurz.tex new file mode 100644 index 0000000..904af65 --- /dev/null +++ b/projektdokumentation/Tabellen/ZeitplanungKurz.tex @@ -0,0 +1,12 @@ +% Table generated by Excel2LaTeX from sheet 'ZeitplanungKurz' +\begin{tabular}{lr} +\rowcolor{heading}\textbf{Projektphase} & \textbf{Geplante Zeit} \\ +Analysephase & 6 h \\ +\rowcolor{odd}Entwurfsphase & 14 h \\ +Implementierungsphase & 37 h \\ +\rowcolor{odd}Abnahmetest der Fachabteilung & 5 h \\ +Erstellen der Dokumentation & 8 h \\ +\hline +\hline +\rowcolor{odd}\textbf{Gesamt} & \textbf{70 h} \\ +\end{tabular} diff --git a/projektdokumentation/Vorlage.tex b/projektdokumentation/Vorlage.tex new file mode 100644 index 0000000..d88115c --- /dev/null +++ b/projektdokumentation/Vorlage.tex @@ -0,0 +1,97 @@ +% !TEX root = Projektdokumentation.tex +\section*{Über diese Vorlage} + +Diese \LaTeX-Vorlage wurde von Stefan Macke\footnote{Blog des Autors: +\url{http://fachinformatiker-anwendungsentwicklung.net}, Twitter: +\Eingabe{@StefanMacke}} als Grundlage für die Projektdokumentationen der Auszubildenden zum Fachinformatiker mit Fachrichtung +Anwendungsentwicklung bei der \AO entwickelt. Nichtsdestotrotz dürfte sie ebenso für die anderen IT-Berufe\footnote{\zB IT-Kaufleute, Fachinformatiker +mit Fachrichtung Systemintegration \usw} geeignet sein, da diese anhand der gleichen Verordnung bewertet werden. + +Diese Vorlage enthält bereits eine Vorstrukturierung der möglichen Inhalte einer tatsächlichen Projektdokumentation, die auf Basis der +Erfahrungen im Rahmen der Prüfertätigkeit des Autors erstellt und unter Zuhilfenahme von \citet{Rohrer2011} abgerundet wurden. + +Sämtliche verwendeten Abbildungen, Tabellen und Listings stammen von \citet{Grashorn2010}. + +Download-Link für diese Vorlage: \url{http://fiae.link/LaTeXVorlageFIAE} + +Auch verfügbar auf GitHub: \url{https://github.com/StefanMacke/latex-vorlage-fiae} + +\subsection*{Lizenz} + +\begin{center} +\includegraphicsKeepAspectRatio{CC-Logo.pdf}{0.3} +\end{center} +Dieses Werk steht unter einer Creative Commons Namensnennung - Weitergabe unter gleichen Bedingungen 4.0 International Lizenz. +\footnote{\url{http://creativecommons.org/licenses/by-sa/4.0/}} + +\begin{center} +\includegraphicsKeepAspectRatio{CC-Attribution.pdf}{0.07} +\includegraphicsKeepAspectRatio{CC-ShareAlike.pdf}{0.07} +\end{center} + +\begin{description} + \item[Namensnennung] Sie müssen den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen. + \footnote{Die Namensnennung im \LaTeX-Quelltext mit Link auf \url{http://fiae.link/LaTeXVorlageFIAE} reicht hierfür aus.} + \item[Weitergabe unter gleichen Bedingungen] Wenn Sie das lizenzierte Werk \bzw den lizenzierten Inhalt bearbeiten + oder in anderer Weise erkennbar als Grundlage für eigenes Schaffen verwenden, dürfen Sie die daraufhin neu entstandenen + Werke \bzw Inhalte nur unter Verwendung von Lizenzbedingungen weitergeben, die mit denen dieses Lizenzvertrages identisch oder vergleichbar sind. +\end{description} + +\subsection*{Inhalt der Projektdokumentation} + +Grundsätzlich definiert die \citet[S.~1746]{Bundesgesetzblatt48}\footnote{Dieses +Dokument sowie alle weiteren hier genannten können unter +\url{http://fiae.link/LaTeXVorlageFIAEQuellen} heruntergeladen werden.} das Ziel der Projektdokumentation wie folgt: +\begin{quote} +"`Durch die Projektarbeit und deren Dokumentation soll der Prüfling belegen, daß er Arbeitsabläufe und Teilaufgaben zielorientiert unter +Beachtung wirtschaftlicher, technischer, organisatorischer und zeitlicher Vorgaben selbständig planen und kundengerecht umsetzen sowie +Dokumentationen kundengerecht anfertigen, zusammenstellen und modifizieren kann."' +\end{quote} + +Und das \citet[S.~36]{BMBF2000} ergänzt: +\begin{quote} +"`Die Ausführung der Projektarbeit wird mit praxisbezogenen Unterlagen dokumentiert. +Der Prüfungsausschuss bewertet die Projektarbeit anhand der Dokumentation. Dabei +wird nicht das Ergebnis -- \zB ein lauffähiges Programm -- herangezogen, sondern +der Arbeitsprozess. Die Dokumentation ist keine wissenschaftliche Abhandlung, +sondern eine handlungsorientierte Darstellung des Projektablaufs mit +praxisbezogenen, d.h. betriebüblichen Unterlagen. Sie soll einen Umfang von +maximal 10 bis 15 DIN A 4-Seiten nicht überschreiten. Soweit erforderlich können in +einem Anhang \zB den Zusammenhang erläuternde Darstellungen beigefügt werden."' +\end{quote} + +Außerdem werden dort die grundlegenden Inhalte der Projektdokumentation aufgelistet: +\begin{itemize} + \item Name und Ausbildungsberuf des Prüfungsteilnehmers + \item Angabe des Ausbildungsbetriebes + \item Thema der Projektarbeit + \item Falls erforderlich, Beschreibung/Konkretisierung des Auftrages + \item Umfassende Beschreibung der Prozessschritte und der erzielten Ergebnisse + \item Gegebenenfalls Veränderungen zum Projektantrag mit Begründung + \item Wenn für das Projekt erforderlich, ein Anhang mit praxisbezogenen Unterlagen und Dokumenten. Dieser Anhang sollte nicht + aufgebläht werden. Die angehängten Dokumente und Unterlagen sind auf das absolute Minimum zu beschränken. +\end{itemize} + +In den folgenden Kapiteln werden diese geforderten Inhalte und sinnvolle Ergänzungen nun meist stichwortartig und \ggfs mit +Beispielen beschrieben. Nicht alle Kapitel müssen in jeder Dokumentation vorhanden sein. Handelt es sich \bspw um ein in sich +geschlossenes Projekt, kann das Kapitel~\ref{sec:Projektabgrenzung}: \nameref{sec:Projektabgrenzung} entfallen; arbeitet die +Anwendung nur mit \acs{XML}-Dateien, kann und muss keine Datenbank beschrieben werden \usw + + +\subsection*{Formale Vorgaben} + +Die formalen Vorgaben zum Umfang und zur Gestaltung der Projektdokumentation können je nach IHK recht unterschiedlich sein. +Normalerweise sollte die zuständige IHK einen Leitfaden bereitstellen, in dem alle Formalien nachgelesen werden können, +wie \zB bei der \citet{MerkblattIHK}. + +Als Richtwert verwende ich 15 Seiten für den reinen Inhalt. Also in dieser Vorlage alle Seiten, die arabisch nummeriert +sind (ohne das Literaturverzeichnis und die eidesstattliche Erklärung). +Große Abbildungen, Quelltexte, Tabellen \usw gehören in den Anhang, der 25 Seiten nicht überschreiten sollte. + +Typographische Konventionen, Seitenränder \usw können in der Datei \Datei{Seitenstil.tex} beliebig angepasst werden. + + +\subsection*{Bewertungskriterien} +Die Bewertungskriterien für die Benotung der Projektdokumentation sind recht einheitlich und können leicht in Erfahrung +gebracht werden, \zB bei der \citet{BewertungsmatrikIHK}. +Grundsätzlich sollte die Projektdokumentation nach der Fertigstellung noch einmal im Hinblick auf diese Kriterien durchgeschaut werden. diff --git a/projektdokumentation/natbib.cfg b/projektdokumentation/natbib.cfg new file mode 100644 index 0000000..b732f45 --- /dev/null +++ b/projektdokumentation/natbib.cfg @@ -0,0 +1,14 @@ +% in dieser Datei wird das Aussehen der Zitate und des Literaturverzeichnisses definiert + +\newcommand{\bibstyle@natdin}% + {\bibpunct{[}{]}{;}{a}{}{,~} % Quellenangaben in eckige Klammern fassen + % nach dieser Definition wird das label (dinatlabel) fett geschrieben, dann Zeilenumbruch; + % darunter der bibliographische Beleg + \gdef\NAT@biblabelnum##1{\textbf{##1}\\}} + +\bibstyle@natdin + +% Einzug der Belege nach der Einordnungsmarke +\setlength{\bibhang}{7mm} + + diff --git a/texput.log b/texput.log new file mode 100644 index 0000000..ffecf13 --- /dev/null +++ b/texput.log @@ -0,0 +1,21 @@ +This is pdfTeX, Version 3.141592653-2.6-1.40.22 (TeX Live 2022/dev/Debian) (preloaded format=pdflatex 2025.6.5) 12 JUN 2025 23:26 +entering extended mode + restricted \write18 enabled. + %&-line parsing enabled. +**Projektdokumentation.tex + +! Emergency stop. +<*> Projektdokumentation.tex + +End of file on the terminal! + + +Here is how much of TeX's memory you used: + 3 strings out of 478287 + 131 string characters out of 5849290 + 289007 words of memory out of 5000000 + 18302 multiletter control sequences out of 15000+600000 + 469259 words of font info for 28 fonts, out of 8000000 for 9000 + 1141 hyphenation exceptions out of 8191 + 0i,0n,0p,1b,6s stack positions out of 5000i,500n,10000p,200000b,80000s +! ==> Fatal error occurred, no output PDF file produced!