Compare commits
88 commits
Author | SHA1 | Date | |
---|---|---|---|
038c6e145a |
|||
cbddfa0552 |
|||
b4df89b7b0 |
|||
|
6807d51e0c | ||
bd6dc99f30 |
|||
|
ed4753c300 | ||
4748196210 |
|||
7e861896dc |
|||
18010b28d1 |
|||
075fd97ae8 |
|||
5cedbcb004 |
|||
7296cfead8 |
|||
7714957a62 | |||
c3f2fcd431 |
|||
07bb68d864 |
|||
eb04c03208 |
|||
|
01b99c04c6 | ||
7221422d3b |
|||
d916fe4942 | |||
9e7fbd1c1e |
|||
0e9e729fdf | |||
dfcf02c1b1 |
|||
|
9c856f02cd |
||
c87a0593b3 |
|||
9ebf0d0036 | |||
ab6265b2cd |
|||
d9309cb735 | |||
9bd5a15480 |
|||
ce3af8915d |
|||
684cec7d56 |
|||
ee9dbb7577 |
|||
|
3dfe318500 | ||
a7b5c42558 |
|||
9207c070fd |
|||
4bcc6b45b4 |
|||
f754a6fc72 |
|||
96aca3b293 | |||
a411234714 |
|||
52a3e6aadc |
|||
5ea3ff73a0 |
|||
050c272226 | |||
75deee3f9f |
|||
|
4af605ef96 | ||
6737eb9e4a |
|||
4619f787f0 |
|||
3ca0b5a3c4 |
|||
7e30e191b4 |
|||
|
140bd44d66 | ||
|
1d6ac261e0 | ||
9deb92ad13 |
|||
0345df3a30 | |||
5bd3f554e2 |
|||
59bb910f05 | |||
e5155c072f |
|||
|
922c95c212 | ||
|
c5b44f3f29 | ||
fad280104d |
|||
|
9cb813bf41 | ||
56763952c0 |
|||
94eaf98250 | |||
45fe90237b |
|||
739c4f610a |
|||
9717e5c43e |
|||
cef0ca2a73 |
|||
0429406383 | |||
332f9d1ce1 |
|||
922d48e1c0 |
|||
b376307272 | |||
111c6c2a64 | |||
e828efdfa7 | |||
971519f99c |
|||
a296ae147b |
|||
|
e4173e3ade |
||
|
ed83097b6b | ||
|
68306f3893 | ||
20076d8fe0 |
|||
0c2a21c218 |
|||
|
65471d1666 | ||
801c60b793 |
|||
feb40e3f79 |
|||
761a527ef0 |
|||
e02f7357f0 |
|||
|
db93ec790e | ||
17774fcdb2 | |||
790eeeb145 |
|||
0b39eacc33 |
|||
04bdb89a93 |
|||
b583eedd75 |
97 changed files with 5124 additions and 249 deletions
|
@ -12,3 +12,9 @@ ci:
|
|||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- ".gitea/**"
|
||||
|
||||
docs:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "projektdokumentation/**"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
29
.gitea/workflows/docs.yml
Normal file
29
.gitea/workflows/docs.yml
Normal file
|
@ -0,0 +1,29 @@
|
|||
name: Build docs
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build-docs:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: git.kjan.de/actions/runner-latex:latest
|
||||
env:
|
||||
# Edit here with the names of your latex file and directory (can use ".")
|
||||
DIR: projektdokumentation
|
||||
FILE: Projektdokumentation.tex
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: LaTeX compile
|
||||
working-directory: ${{ env.DIR }}
|
||||
run: latexmk -pdf ${{ env.FILE }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: https://git.kjan.de/actions/upload-artifact@v3 # Do not upgrade
|
||||
with:
|
||||
name: Doku
|
||||
path: projektdokumentation/Projektdokumentation.pdf
|
|
@ -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<Test> {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package de.szut.casino.security.oauth2.github;
|
||||
|
||||
import de.szut.casino.deposit.TransactionEntity;
|
||||
import de.szut.casino.deposit.TransactionRepository;
|
||||
import de.szut.casino.deposit.TransactionStatus;
|
||||
import de.szut.casino.security.dto.AuthResponseDto;
|
||||
import de.szut.casino.security.jwt.JwtUtils;
|
||||
import de.szut.casino.user.AuthProvider;
|
||||
|
@ -30,12 +33,14 @@ public class GitHubService {
|
|||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final UserRepository userRepository;
|
||||
private final TransactionRepository transactionRepository;
|
||||
private final JwtUtils jwtUtils;
|
||||
private final PasswordEncoder oauth2PasswordEncoder;
|
||||
|
||||
public GitHubService(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtils jwtUtils, PasswordEncoder oauth2PasswordEncoder) {
|
||||
public GitHubService(AuthenticationManager authenticationManager, UserRepository userRepository, TransactionRepository transactionRepository, JwtUtils jwtUtils, PasswordEncoder oauth2PasswordEncoder) {
|
||||
this.authenticationManager = authenticationManager;
|
||||
this.userRepository = userRepository;
|
||||
this.transactionRepository = transactionRepository;
|
||||
this.jwtUtils = jwtUtils;
|
||||
this.oauth2PasswordEncoder = oauth2PasswordEncoder;
|
||||
}
|
||||
|
@ -139,15 +144,20 @@ public class GitHubService {
|
|||
user.setProvider(AuthProvider.GITHUB);
|
||||
user.setProviderId(githubId);
|
||||
user.setEmailVerified(true);
|
||||
|
||||
user.setBalance(new BigDecimal("1000.00"));
|
||||
user.setBalance(new BigDecimal("100.00"));
|
||||
}
|
||||
}
|
||||
|
||||
String randomPassword = UUID.randomUUID().toString();
|
||||
user.setPassword(oauth2PasswordEncoder.encode(randomPassword));
|
||||
TransactionEntity transaction = new TransactionEntity();
|
||||
transaction.setAmount(100L);
|
||||
transaction.setUser(user);
|
||||
transaction.setSessionId("signup_bonus");
|
||||
transaction.setStatus(TransactionStatus.SUCCEEDED);
|
||||
|
||||
userRepository.save(user);
|
||||
transactionRepository.save(transaction);
|
||||
|
||||
Authentication authentication = this.authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getEmail(), randomPassword));
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package de.szut.casino.security.oauth2.google;
|
||||
|
||||
import de.szut.casino.deposit.TransactionEntity;
|
||||
import de.szut.casino.deposit.TransactionRepository;
|
||||
import de.szut.casino.deposit.TransactionStatus;
|
||||
import de.szut.casino.security.dto.AuthResponseDto;
|
||||
import de.szut.casino.security.jwt.JwtUtils;
|
||||
import de.szut.casino.user.AuthProvider;
|
||||
|
@ -47,12 +50,14 @@ public class GoogleService {
|
|||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final UserRepository userRepository;
|
||||
private final TransactionRepository transactionRepository;
|
||||
private final JwtUtils jwtUtils;
|
||||
private final PasswordEncoder oauth2PasswordEncoder;
|
||||
|
||||
public GoogleService(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtils jwtUtils, PasswordEncoder oauth2PasswordEncoder) {
|
||||
public GoogleService(AuthenticationManager authenticationManager, UserRepository userRepository, TransactionRepository transactionRepository, JwtUtils jwtUtils, PasswordEncoder oauth2PasswordEncoder) {
|
||||
this.authenticationManager = authenticationManager;
|
||||
this.userRepository = userRepository;
|
||||
this.transactionRepository = transactionRepository;
|
||||
this.jwtUtils = jwtUtils;
|
||||
this.oauth2PasswordEncoder = oauth2PasswordEncoder;
|
||||
}
|
||||
|
@ -146,8 +151,14 @@ public class GoogleService {
|
|||
|
||||
String randomPassword = UUID.randomUUID().toString();
|
||||
user.setPassword(oauth2PasswordEncoder.encode(randomPassword));
|
||||
TransactionEntity transaction = new TransactionEntity();
|
||||
transaction.setAmount(100L);
|
||||
transaction.setUser(user);
|
||||
transaction.setSessionId("signup_bonus");
|
||||
transaction.setStatus(TransactionStatus.SUCCEEDED);
|
||||
|
||||
userRepository.save(user);
|
||||
transactionRepository.save(transaction);
|
||||
|
||||
Authentication authentication = authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(user.getEmail(), randomPassword)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package de.szut.casino.user;
|
||||
|
||||
import de.szut.casino.deposit.TransactionEntity;
|
||||
import de.szut.casino.deposit.TransactionStatus;
|
||||
import de.szut.casino.exceptionHandling.exceptions.UserNotFoundException;
|
||||
import de.szut.casino.user.dto.CreateUserDto;
|
||||
import jakarta.persistence.EntityExistsException;
|
||||
|
@ -38,6 +40,12 @@ public class UserService {
|
|||
RandomStringUtils.randomAlphanumeric(64)
|
||||
);
|
||||
|
||||
TransactionEntity transaction = new TransactionEntity();
|
||||
transaction.setAmount(100L);
|
||||
transaction.setUser(user);
|
||||
transaction.setSessionId("signup_bonus");
|
||||
transaction.setStatus(TransactionStatus.SUCCEEDED);
|
||||
|
||||
return userRepository.save(user);
|
||||
}
|
||||
|
||||
|
|
58
backend/src/main/resources/application-inmemory.properties
Normal file
58
backend/src/main/resources/application-inmemory.properties
Normal file
|
@ -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
|
||||
|
3
frontend/.gitignore
vendored
3
frontend/.gitignore
vendored
|
@ -29,6 +29,9 @@ yarn-error.log
|
|||
.history/*
|
||||
|
||||
# Miscellaneous
|
||||
/.claude
|
||||
/test-results
|
||||
/playwright-report
|
||||
/.angular/cache
|
||||
.sass-cache/
|
||||
/connect.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=="],
|
||||
}
|
||||
}
|
||||
|
|
8
frontend/e2e/backend.spec.ts
Normal file
8
frontend/e2e/backend.spec.ts
Normal file
|
@ -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();
|
||||
});
|
36
frontend/e2e/homepage.spec.ts
Normal file
36
frontend/e2e/homepage.spec.ts
Normal file
|
@ -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();
|
||||
});
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
49
frontend/playwright.config.ts
Normal file
49
frontend/playwright.config.ts
Normal file
|
@ -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
|
||||
// },
|
||||
},
|
||||
});
|
|
@ -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<void>();
|
||||
@Output() forgotPassword = new EventEmitter<void>();
|
||||
|
||||
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]],
|
||||
|
|
|
@ -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<void>();
|
||||
@Output() switchToLogin = new EventEmitter<void>();
|
||||
|
||||
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]],
|
||||
});
|
||||
|
|
|
@ -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<void>();
|
||||
@Output() closeDialog = new EventEmitter<void>();
|
||||
|
||||
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)]],
|
||||
|
|
|
@ -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']) {
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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']) {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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<DiceResult> {
|
||||
return this.http.post<DiceResult>(this.apiUrl, diceDto);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -21,13 +21,7 @@
|
|||
(click)="showRegisterForm()"
|
||||
class="w-full sm:w-auto button-primary px-6 sm:px-8 py-3 shadow-lg"
|
||||
>
|
||||
Konto erstellen
|
||||
</button>
|
||||
<button
|
||||
(click)="showLoginForm()"
|
||||
class="w-full sm:w-auto bg-slate-700 text-white hover:bg-slate-600 px-6 sm:px-8 py-3 shadow-lg rounded"
|
||||
>
|
||||
Anmelden
|
||||
Jetzt registrieren
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
@ -46,33 +40,63 @@
|
|||
<div class="game-card-content">
|
||||
<h3 class="game-heading-sm">Slots</h3>
|
||||
<p class="game-text">Klassische Spielautomaten</p>
|
||||
<a
|
||||
routerLink="game/slots"
|
||||
class="button-primary w-full py-2 inline-block text-center"
|
||||
>Jetzt Spielen</a
|
||||
>
|
||||
@if (isLoggedIn()) {
|
||||
<a
|
||||
routerLink="game/slots"
|
||||
class="button-primary w-full py-2 inline-block text-center"
|
||||
>
|
||||
Jetzt Spielen
|
||||
</a>
|
||||
} @else {
|
||||
<button
|
||||
(click)="showLoginForm()"
|
||||
class="button-primary w-full py-2 inline-block text-center"
|
||||
>
|
||||
Jetzt Spielen
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden lg:block card">
|
||||
<div class="game-card-content">
|
||||
<h3 class="game-heading-sm">Blackjack</h3>
|
||||
<p class="game-text">Klassisches Kartenspiel</p>
|
||||
<a
|
||||
routerLink="game/blackjack"
|
||||
class="button-primary w-full py-2 inline-block text-center"
|
||||
>Jetzt Spielen</a
|
||||
>
|
||||
@if (isLoggedIn()) {
|
||||
<a
|
||||
routerLink="game/blackjack"
|
||||
class="button-primary w-full py-2 inline-block text-center"
|
||||
>
|
||||
Jetzt Spielen
|
||||
</a>
|
||||
} @else {
|
||||
<button
|
||||
(click)="showLoginForm()"
|
||||
class="button-primary w-full py-2 inline-block text-center"
|
||||
>
|
||||
Jetzt Spielen
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden lg:block card">
|
||||
<div class="game-card-content">
|
||||
<h3 class="game-heading-sm">Coinflip</h3>
|
||||
<p class="game-text">Münzwurf</p>
|
||||
<a
|
||||
routerLink="game/blackjack"
|
||||
class="button-primary w-full py-2 inline-block text-center"
|
||||
>Jetzt Spielen</a
|
||||
>
|
||||
@if (isLoggedIn()) {
|
||||
<a
|
||||
routerLink="game/coinflip"
|
||||
class="button-primary w-full py-2 inline-block text-center"
|
||||
>
|
||||
Jetzt Spielen
|
||||
</a>
|
||||
} @else {
|
||||
<button
|
||||
(click)="showLoginForm()"
|
||||
class="button-primary w-full py-2 inline-block text-center"
|
||||
>
|
||||
Jetzt Spielen
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -82,22 +106,42 @@
|
|||
<div class="game-card-content">
|
||||
<h3 class="game-heading-sm">Dice</h3>
|
||||
<p class="game-text">Würfelspiel</p>
|
||||
<a
|
||||
routerLink="/game/dice"
|
||||
class="button-primary w-full py-2 inline-block text-center"
|
||||
>Jetzt Spielen</a
|
||||
>
|
||||
@if (isLoggedIn()) {
|
||||
<a
|
||||
routerLink="game/dice"
|
||||
class="button-primary w-full py-2 inline-block text-center"
|
||||
>
|
||||
Jetzt Spielen
|
||||
</a>
|
||||
} @else {
|
||||
<button
|
||||
(click)="showLoginForm()"
|
||||
class="button-primary w-full py-2 inline-block text-center"
|
||||
>
|
||||
Jetzt Spielen
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden lg:block card">
|
||||
<div class="game-card-content">
|
||||
<h3 class="game-heading-sm">Lootboxen</h3>
|
||||
<p class="game-text">Überraschungskisten</p>
|
||||
<a
|
||||
routerLink="game/lootboxes"
|
||||
class="button-primary w-full py-2 inline-block text-center"
|
||||
>Jetzt Spielen</a
|
||||
>
|
||||
@if (isLoggedIn()) {
|
||||
<a
|
||||
routerLink="game/lootboxes"
|
||||
class="button-primary w-full py-2 inline-block text-center"
|
||||
>
|
||||
Jetzt Spielen
|
||||
</a>
|
||||
} @else {
|
||||
<button
|
||||
(click)="showLoginForm()"
|
||||
class="button-primary w-full py-2 inline-block text-center"
|
||||
>
|
||||
Jetzt Spielen
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -23,15 +23,14 @@ import RecoverPasswordComponent from '../auth/recover-password/recover-password.
|
|||
})
|
||||
export class LandingComponent implements OnInit, OnDestroy {
|
||||
currentSlide = 0;
|
||||
private autoplayInterval: ReturnType<typeof setInterval> | undefined;
|
||||
authService: AuthService = inject(AuthService);
|
||||
route: ActivatedRoute = inject(ActivatedRoute);
|
||||
showLogin = signal(false);
|
||||
showRegister = signal(false);
|
||||
showRecoverPassword = signal(false);
|
||||
isLoggedIn = signal(this.authService.isLoggedIn());
|
||||
|
||||
ngOnInit() {
|
||||
this.startAutoplay();
|
||||
document.body.style.overflow = 'auto';
|
||||
if (this.route.snapshot.queryParamMap.get('login') === 'true') {
|
||||
this.showLoginForm();
|
||||
|
@ -39,7 +38,6 @@ export class LandingComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.stopAutoplay();
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
|
||||
|
@ -73,33 +71,13 @@ export class LandingComponent implements OnInit, OnDestroy {
|
|||
|
||||
prevSlide() {
|
||||
this.currentSlide = this.currentSlide === 0 ? 1 : 0;
|
||||
this.resetAutoplay();
|
||||
}
|
||||
|
||||
nextSlide() {
|
||||
this.currentSlide = this.currentSlide === 1 ? 0 : 1;
|
||||
this.resetAutoplay();
|
||||
}
|
||||
|
||||
goToSlide(index: number) {
|
||||
this.currentSlide = index;
|
||||
this.resetAutoplay();
|
||||
}
|
||||
|
||||
private startAutoplay() {
|
||||
this.autoplayInterval = setInterval(() => {
|
||||
this.nextSlide();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
private stopAutoplay() {
|
||||
if (this.autoplayInterval) {
|
||||
clearInterval(this.autoplayInterval);
|
||||
}
|
||||
}
|
||||
|
||||
private resetAutoplay() {
|
||||
this.stopAutoplay();
|
||||
this.startAutoplay();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<User | null>;
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute
|
||||
) {
|
||||
constructor() {
|
||||
this.userSubject = new BehaviorSubject<User | null>(this.getUserFromStorage());
|
||||
|
||||
// Check for token in URL (OAuth callback) on initialization
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<nav class="bg-deep-blue-light border-b border-emerald-500/30 shadow-lg">
|
||||
<div class="max-w-full mx-auto px-6">
|
||||
<div class="flex justify-between items-center h-16">
|
||||
<div class="flex items-center space-x-6">
|
||||
<div class="flex items-center space-x-2">
|
||||
<a routerLink="/" class="flex items-center space-x-3 group">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xl font-bold text-white"> Trustworthy Casino </span>
|
||||
|
@ -10,17 +10,27 @@
|
|||
</a>
|
||||
|
||||
<div class="hidden md:flex items-center space-x-1">
|
||||
<a
|
||||
routerLink="/home"
|
||||
class="flex items-center px-4 py-2 text-white/90 hover:text-white font-medium rounded-lg hover:bg-white/10 transition-colors duration-200"
|
||||
>
|
||||
<img class="mr-2 w-4 h-4" src="assets/games.svg" alt="gamess" />
|
||||
Spiele
|
||||
</a>
|
||||
@if (isLoggedIn()) {
|
||||
<a
|
||||
routerLink="/home"
|
||||
class="flex items-center px-4 py-2 text-white/90 hover:text-white font-medium rounded-lg hover:bg-white/10 transition-colors duration-200"
|
||||
>
|
||||
<img class="mr-2 w-4 h-4" src="assets/games.svg" alt="gamess" />
|
||||
Spiele
|
||||
</a>
|
||||
} @else {
|
||||
<button
|
||||
(click)="showLogin.emit()"
|
||||
class="flex items-center px-4 py-2 text-white/90 hover:text-white font-medium rounded-lg hover:bg-white/10 transition-colors duration-200"
|
||||
>
|
||||
<img class="mr-2 w-4 h-4" src="assets/games.svg" alt="gamess" />
|
||||
Spiele
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hidden md:flex items-center space-x-2">
|
||||
<div class="hidden md:flex items-center space-x-1">
|
||||
@if (!isLoggedIn()) {
|
||||
<button
|
||||
(click)="showLogin.emit()"
|
||||
|
@ -40,21 +50,13 @@
|
|||
(click)="showRegister.emit()"
|
||||
class="flex items-center px-4 py-2 bg-emerald-600 text-white font-medium rounded-lg hover:bg-emerald-500 transition-colors duration-200"
|
||||
>
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"
|
||||
/>
|
||||
</svg>
|
||||
Registrieren
|
||||
Jetzt registrieren
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (isLoggedIn()) {
|
||||
<div
|
||||
class="flex items-center px-4 py-2 bg-slate-700 border border-emerald-500/30 rounded-lg font-medium"
|
||||
class="flex items-center px-4 py-2 mr-2 bg-slate-700 border border-emerald-500/30 rounded-lg font-medium"
|
||||
>
|
||||
<span class="text-emerald-400 text-sm mr-2">Guthaben:</span>
|
||||
<span
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -52,7 +52,7 @@ a {
|
|||
}
|
||||
|
||||
.slider-grid {
|
||||
@apply min-w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4;
|
||||
@apply min-w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4;
|
||||
}
|
||||
|
||||
.welcome-bonus {
|
||||
|
|
3
justfile
3
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:
|
||||
|
|
132
projektdokumentation/.gitignore
vendored
Normal file
132
projektdokumentation/.gitignore
vendored
Normal file
|
@ -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)
|
17
projektdokumentation/.project
Normal file
17
projektdokumentation/.project
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>VorlageFachinformatiker</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>net.sourceforge.texlipse.builder.TexlipseBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>net.sourceforge.texlipse.builder.TexlipseNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
13
projektdokumentation/.texlipse
Normal file
13
projektdokumentation/.texlipse
Normal file
|
@ -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=
|
23
projektdokumentation/Abkuerzungen.tex
Normal file
23
projektdokumentation/Abkuerzungen.tex
Normal file
|
@ -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}
|
85
projektdokumentation/Allgemein/Befehle.tex
Normal file
85
projektdokumentation/Allgemein/Befehle.tex
Normal file
|
@ -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}}
|
196
projektdokumentation/Allgemein/Packages.tex
Normal file
196
projektdokumentation/Allgemein/Packages.tex
Normal file
|
@ -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]{/*}{*/},
|
||||
}
|
83
projektdokumentation/Allgemein/Seitenstil.tex
Normal file
83
projektdokumentation/Allgemein/Seitenstil.tex
Normal file
|
@ -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}
|
3
projektdokumentation/Allgemein/Silbentrennung.tex
Normal file
3
projektdokumentation/Allgemein/Silbentrennung.tex
Normal file
|
@ -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
|
2234
projektdokumentation/Allgemein/natdin.bst
Normal file
2234
projektdokumentation/Allgemein/natdin.bst
Normal file
File diff suppressed because it is too large
Load diff
35
projektdokumentation/Anhang.tex
Normal file
35
projektdokumentation/Anhang.tex
Normal file
|
@ -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
|
15
projektdokumentation/Anhang/AnhangBenutzerDoku.tex
Normal file
15
projektdokumentation/Anhang/AnhangBenutzerDoku.tex
Normal file
|
@ -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}
|
||||
|
7
projektdokumentation/Anhang/AnhangDoc.tex
Normal file
7
projektdokumentation/Anhang/AnhangDoc.tex
Normal file
|
@ -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}
|
19
projektdokumentation/Anhang/AnhangEntwuerfe.tex
Normal file
19
projektdokumentation/Anhang/AnhangEntwuerfe.tex
Normal file
|
@ -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}
|
22
projektdokumentation/Anhang/AnhangLastenheft.tex
Normal file
22
projektdokumentation/Anhang/AnhangLastenheft.tex
Normal file
|
@ -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}
|
||||
|
52
projektdokumentation/Anhang/AnhangPflichtenheft.tex
Normal file
52
projektdokumentation/Anhang/AnhangPflichtenheft.tex
Normal file
|
@ -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}
|
28
projektdokumentation/Anhang/AnhangRessourcen.tex
Normal file
28
projektdokumentation/Anhang/AnhangRessourcen.tex
Normal file
|
@ -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}
|
14
projektdokumentation/Anhang/AnhangScreenshots.tex
Normal file
14
projektdokumentation/Anhang/AnhangScreenshots.tex
Normal file
|
@ -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
|
9
projektdokumentation/Anhang/AnhangTest.tex
Normal file
9
projektdokumentation/Anhang/AnhangTest.tex
Normal file
|
@ -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}
|
4
projektdokumentation/Befehle.tex
Normal file
4
projektdokumentation/Befehle.tex
Normal file
|
@ -0,0 +1,4 @@
|
|||
% Abkürzungen
|
||||
\newcommand{\Versis}{\textsc{Versis}\xspace}
|
||||
\newcommand{\NI}{NatInfo\xspace}
|
||||
\newcommand{\AO}{\textsc{Alte Oldenburger} Krankenversicherung\xspace}
|
92
projektdokumentation/Bibliographie.bib
Normal file
92
projektdokumentation/Bibliographie.bib
Normal file
|
@ -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/}
|
||||
}
|
BIN
projektdokumentation/Bilder/LogoBetrieb.pdf
Normal file
BIN
projektdokumentation/Bilder/LogoBetrieb.pdf
Normal file
Binary file not shown.
BIN
projektdokumentation/Bilder/component-diagram.drawio.png
Normal file
BIN
projektdokumentation/Bilder/component-diagram.drawio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 261 KiB |
BIN
projektdokumentation/Bilder/container-diagram.drawio.png
Normal file
BIN
projektdokumentation/Bilder/container-diagram.drawio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 84 KiB |
BIN
projektdokumentation/Bilder/hitec-logo.png
Normal file
BIN
projektdokumentation/Bilder/hitec-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 MiB |
BIN
projektdokumentation/Bilder/login.png
Executable file
BIN
projektdokumentation/Bilder/login.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
projektdokumentation/Bilder/oauth.png
Executable file
BIN
projektdokumentation/Bilder/oauth.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
27
projektdokumentation/Deckblatt.tex
Normal file
27
projektdokumentation/Deckblatt.tex
Normal file
|
@ -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}
|
16
projektdokumentation/Inhalt.tex
Normal file
16
projektdokumentation/Inhalt.tex
Normal file
|
@ -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}
|
12
projektdokumentation/Inhalt/Abschluss.tex
Normal file
12
projektdokumentation/Inhalt/Abschluss.tex
Normal file
|
@ -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
|
23
projektdokumentation/Inhalt/Auth.tex
Normal file
23
projektdokumentation/Inhalt/Auth.tex
Normal file
|
@ -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}
|
52
projektdokumentation/Inhalt/Blackjack.tex
Normal file
52
projektdokumentation/Inhalt/Blackjack.tex
Normal file
|
@ -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.
|
53
projektdokumentation/Inhalt/CI.tex
Normal file
53
projektdokumentation/Inhalt/CI.tex
Normal file
|
@ -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.
|
41
projektdokumentation/Inhalt/Coinflip.tex
Normal file
41
projektdokumentation/Inhalt/Coinflip.tex
Normal file
|
@ -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.
|
5
projektdokumentation/Inhalt/Deployment.tex
Normal file
5
projektdokumentation/Inhalt/Deployment.tex
Normal file
|
@ -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}).
|
43
projektdokumentation/Inhalt/Dice.tex
Normal file
43
projektdokumentation/Inhalt/Dice.tex
Normal file
|
@ -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}
|
57
projektdokumentation/Inhalt/Einleitung.tex
Normal file
57
projektdokumentation/Inhalt/Einleitung.tex
Normal file
|
@ -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.
|
78
projektdokumentation/Inhalt/Lootboxes.tex
Normal file
78
projektdokumentation/Inhalt/Lootboxes.tex
Normal file
|
@ -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.
|
40
projektdokumentation/Inhalt/Projektarchitektur.tex
Normal file
40
projektdokumentation/Inhalt/Projektarchitektur.tex
Normal file
|
@ -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.
|
56
projektdokumentation/Inhalt/Slots.tex
Normal file
56
projektdokumentation/Inhalt/Slots.tex
Normal file
|
@ -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}
|
27
projektdokumentation/Inhalt/Vorbereitung.tex
Normal file
27
projektdokumentation/Inhalt/Vorbereitung.tex
Normal file
|
@ -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.
|
67
projektdokumentation/Inhalt/WirtschaftlicheBetrachtung.tex
Normal file
67
projektdokumentation/Inhalt/WirtschaftlicheBetrachtung.tex
Normal file
|
@ -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.
|
98
projektdokumentation/LICENSE.md
Normal file
98
projektdokumentation/LICENSE.md
Normal file
|
@ -0,0 +1,98 @@
|
|||
[](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.
|
247
projektdokumentation/Listings/CoinflipComponent.ts
Normal file
247
projektdokumentation/Listings/CoinflipComponent.ts
Normal file
|
@ -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<CoinflipGame | null>(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<CoinflipGame>('/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';
|
||||
}
|
||||
}
|
38
projektdokumentation/Listings/CoinflipController.java
Normal file
38
projektdokumentation/Listings/CoinflipController.java
Normal file
|
@ -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<Object> 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));
|
||||
}
|
||||
}
|
35
projektdokumentation/Listings/CoinflipService.java
Normal file
35
projektdokumentation/Listings/CoinflipService.java
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
92
projektdokumentation/Listings/UserEntity.java
Normal file
92
projektdokumentation/Listings/UserEntity.java
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
53
projektdokumentation/Listings/application.properties
Normal file
53
projektdokumentation/Listings/application.properties
Normal file
|
@ -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
|
14
projektdokumentation/Meta.tex
Normal file
14
projektdokumentation/Meta.tex
Normal file
|
@ -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}
|
63
projektdokumentation/Projektdokumentation.tex
Normal file
63
projektdokumentation/Projektdokumentation.tex
Normal file
|
@ -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}
|
12
projektdokumentation/README.md
Normal file
12
projektdokumentation/README.md
Normal file
|
@ -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
|
||||
|
||||
[](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/).
|
11
projektdokumentation/Tabellen/Kostenaufstellung.tex
Normal file
11
projektdokumentation/Tabellen/Kostenaufstellung.tex
Normal file
|
@ -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}
|
11
projektdokumentation/Tabellen/Nutzwert.tex
Normal file
11
projektdokumentation/Tabellen/Nutzwert.tex
Normal file
|
@ -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}
|
9
projektdokumentation/Tabellen/Zeitersparnis.tex
Normal file
9
projektdokumentation/Tabellen/Zeitersparnis.tex
Normal file
|
@ -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}
|
13
projektdokumentation/Tabellen/Zeitnachher.tex
Normal file
13
projektdokumentation/Tabellen/Zeitnachher.tex
Normal file
|
@ -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}
|
48
projektdokumentation/Tabellen/ZeitplanungKomplett.tex
Normal file
48
projektdokumentation/Tabellen/ZeitplanungKomplett.tex
Normal file
|
@ -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}
|
12
projektdokumentation/Tabellen/ZeitplanungKurz.tex
Normal file
12
projektdokumentation/Tabellen/ZeitplanungKurz.tex
Normal file
|
@ -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}
|
97
projektdokumentation/Vorlage.tex
Normal file
97
projektdokumentation/Vorlage.tex
Normal file
|
@ -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.
|
14
projektdokumentation/natbib.cfg
Normal file
14
projektdokumentation/natbib.cfg
Normal file
|
@ -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}
|
||||
|
||||
|
21
texput.log
Normal file
21
texput.log
Normal file
|
@ -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!
|
Reference in a new issue