Merge branch 'main' into docs/preperation
All checks were successful
CI / Get Changed Files (pull_request) Successful in 10s
CI / eslint (pull_request) Has been skipped
CI / oxlint (pull_request) Has been skipped
Pull Request Labeler / labeler (pull_request_target) Successful in 5s
CI / prettier (pull_request) Has been skipped
CI / Backend Tests (pull_request) Has been skipped
CI / test-build (pull_request) Has been skipped
CI / Checkstyle Main (pull_request) Has been skipped
CI / Docker frontend validation (pull_request) Has been skipped
CI / Docker backend validation (pull_request) Has been skipped
CI / Playwright (pull_request) Has been skipped
Label PRs based on size / Check PR size (pull_request) Successful in 21s
Claude PR Review / claude-code (pull_request) Successful in 41s
Build docs / build-docs (pull_request) Successful in 5m56s
|
@ -16,4 +16,5 @@ ci:
|
||||||
docs:
|
docs:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- "docs/**"
|
- "projektdokumentation/**"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
name: Build docs
|
name: Build docs
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
pull_request:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
|
||||||
|
@ -11,18 +12,18 @@ jobs:
|
||||||
image: git.kjan.de/actions/runner-latex:latest
|
image: git.kjan.de/actions/runner-latex:latest
|
||||||
env:
|
env:
|
||||||
# Edit here with the names of your latex file and directory (can use ".")
|
# Edit here with the names of your latex file and directory (can use ".")
|
||||||
DIR: docs
|
DIR: projektdokumentation
|
||||||
FILE: projektdokumentation.tex
|
FILE: Projektdokumentation.tex
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: LaTeX compile
|
- name: LaTeX compile
|
||||||
working-directory: ${{ env.DIR }}
|
working-directory: ${{ env.DIR }}
|
||||||
run: latexmk -pdf -xelatex ${{ env.FILE }}
|
run: latexmk -pdf ${{ env.FILE }}
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: https://git.kjan.de/actions/upload-artifact@v3 # Do not upgrade
|
uses: https://git.kjan.de/actions/upload-artifact@v3 # Do not upgrade
|
||||||
with:
|
with:
|
||||||
name: Doku
|
name: Doku
|
||||||
path: docs/projektdokumentation.pdf
|
path: projektdokumentation/Projektdokumentation.pdf
|
||||||
|
|
|
@ -50,7 +50,7 @@ dependencies {
|
||||||
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server:3.5.0")
|
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")
|
implementation("org.springframework.boot:spring-boot-starter-oauth2-client:3.5.0")
|
||||||
runtimeOnly("org.postgresql:postgresql")
|
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")
|
implementation("io.jsonwebtoken:jjwt-api:0.12.6")
|
||||||
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6")
|
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6")
|
||||||
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.6")
|
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.6")
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
"@types/jasmine": "~5.1.0",
|
"@types/jasmine": "~5.1.0",
|
||||||
"angular-eslint": "20.0.0",
|
"angular-eslint": "20.0.0",
|
||||||
"eslint": "^9.28.0",
|
"eslint": "^9.28.0",
|
||||||
"jasmine-core": "~5.7.0",
|
"jasmine-core": "~5.8.0",
|
||||||
"karma": "~6.4.0",
|
"karma": "~6.4.0",
|
||||||
"karma-chrome-launcher": "~3.2.0",
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
"karma-coverage": "~2.2.0",
|
"karma-coverage": "~2.2.0",
|
||||||
|
@ -44,7 +44,7 @@
|
||||||
"karma-jasmine-html-reporter": "~2.1.0",
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"typescript": "~5.8.0",
|
"typescript": "~5.8.0",
|
||||||
"typescript-eslint": "8.33.1",
|
"typescript-eslint": "8.34.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -687,9 +687,9 @@
|
||||||
|
|
||||||
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
"@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=="],
|
"@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=="],
|
||||||
|
|
||||||
|
@ -697,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/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=="],
|
"@typescript-eslint/types": ["@typescript-eslint/types@8.33.1", "", {}, "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg=="],
|
||||||
|
|
||||||
|
@ -705,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/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=="],
|
"@vitejs/plugin-basic-ssl": ["@vitejs/plugin-basic-ssl@2.0.0", "", { "peerDependencies": { "vite": "^6.0.0" } }, "sha512-gc9Tjg8bUxBVSTzeWT3Njc0Cl3PakHFKdNfABnZWiUgbxqmHDEn7uECv3fHVylxoYgNzAcmU7ZrILz+BwSo3sA=="],
|
||||||
|
|
||||||
|
@ -1221,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=="],
|
"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=="],
|
"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=="],
|
||||||
|
|
||||||
|
@ -1755,7 +1755,7 @@
|
||||||
|
|
||||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
"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=="],
|
"ua-parser-js": ["ua-parser-js@0.7.40", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-us1E3K+3jJppDBa3Tl0L3MOJiGhe1C6P0+nIvQAFYbxlMAx0h81eOwLmU57xgqToduDDPx3y5QsdjPfDu+FgOQ=="],
|
||||||
|
|
||||||
|
@ -1947,10 +1947,30 @@
|
||||||
|
|
||||||
"@tufjs/models/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
"@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/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/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=="],
|
"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=="],
|
"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=="],
|
||||||
|
@ -2121,6 +2141,8 @@
|
||||||
|
|
||||||
"tar/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
|
"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/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=="],
|
"vite/tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
||||||
|
@ -2185,6 +2207,30 @@
|
||||||
|
|
||||||
"@tufjs/models/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
"@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=="],
|
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
||||||
|
|
||||||
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||||
|
@ -2269,6 +2315,12 @@
|
||||||
|
|
||||||
"tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
"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/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/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||||
|
@ -2301,6 +2353,16 @@
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"@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=="],
|
||||||
|
|
||||||
|
"@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=="],
|
||||||
|
|
||||||
|
"@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=="],
|
||||||
|
|
||||||
|
"@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=="],
|
||||||
|
|
||||||
|
"@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=="],
|
"cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
||||||
|
|
||||||
"karma-coverage/istanbul-lib-instrument/@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=="],
|
"karma-coverage/istanbul-lib-instrument/@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=="],
|
||||||
|
@ -2317,14 +2379,24 @@
|
||||||
|
|
||||||
"karma/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
"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=="],
|
"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=="],
|
"yargs/string-width/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=="],
|
"@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=="],
|
||||||
|
|
||||||
|
"@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/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=="],
|
"karma/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
"@types/jasmine": "~5.1.0",
|
"@types/jasmine": "~5.1.0",
|
||||||
"angular-eslint": "20.0.0",
|
"angular-eslint": "20.0.0",
|
||||||
"eslint": "^9.28.0",
|
"eslint": "^9.28.0",
|
||||||
"jasmine-core": "~5.7.0",
|
"jasmine-core": "~5.8.0",
|
||||||
"karma": "~6.4.0",
|
"karma": "~6.4.0",
|
||||||
"karma-chrome-launcher": "~3.2.0",
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
"karma-coverage": "~2.2.0",
|
"karma-coverage": "~2.2.0",
|
||||||
|
@ -54,6 +54,6 @@
|
||||||
"karma-jasmine-html-reporter": "~2.1.0",
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"typescript": "~5.8.0",
|
"typescript": "~5.8.0",
|
||||||
"typescript-eslint": "8.33.1"
|
"typescript-eslint": "8.34.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,28 +13,11 @@
|
||||||
% Die Option (in den eckigen Klammern) enthält das längste Label oder
|
% Die Option (in den eckigen Klammern) enthält das längste Label oder
|
||||||
% einen Platzhalter der die Breite der linken Spalte bestimmt.
|
% einen Platzhalter der die Breite der linken Spalte bestimmt.
|
||||||
\begin{acronym}[WWWWW]
|
\begin{acronym}[WWWWW]
|
||||||
\acro{API}{Application Programming Interface}
|
|
||||||
\acro{CI}{Continuous Integration}
|
\acro{CI}{Continuous Integration}
|
||||||
\acro{CI/CD}{Continuous Integration/Continuous Deployment}
|
\acro{CI/CD}{Continuous Integration/Continuous Deployment}
|
||||||
\acro{CLI}{Command Line Interface}
|
|
||||||
\acro{CRM}{Customer Relationship Management}
|
|
||||||
\acro{CRON}{Vorgangsausführung gemäß geplanten Zeitabläufen für UNIX Programme}
|
|
||||||
\acro{E2E}{End-to-End}
|
\acro{E2E}{End-to-End}
|
||||||
\acro{eCommerce}{Electronic Commerce}
|
|
||||||
\acro{ERM}{Entity-Relationship-Model}
|
|
||||||
\acro{GUI}{Graphical User Interface}
|
|
||||||
\acro{HTTP}{Hypertext Transfer Protocol}
|
|
||||||
\acro{IDE}{Integrated Development Environment}
|
|
||||||
\acro{IX}{Intex Fusion Pro Omnichannel CRM}
|
|
||||||
\acro{JSON}{JavaScript Object Notation}
|
|
||||||
\acro{M2}{\textsc{Magento 2} eCommerce Platform}
|
|
||||||
\acro{NSD}{\textsc{neusta software development} GmbH}
|
|
||||||
\acro{NXP}{\textsc{neusta experience} GmbH}
|
|
||||||
\acro{PHP}{Hypertext Preprocessor}\acused{PHP}
|
|
||||||
\acro{SQL}{Structured Query Language}
|
|
||||||
\acro{URL}{Uniform Resource Locator}\acused{URL}
|
|
||||||
\acro{VM}{Virtual Machine}
|
|
||||||
\acro{XML}{Extensible Markup Language}
|
|
||||||
\acro{API}{Application Programming Interface}
|
\acro{API}{Application Programming Interface}
|
||||||
|
\acro{JSON}{JavaScript Object Notation}
|
||||||
|
\acro{HTTP}{Hypertext Transfer Protocol}
|
||||||
\acro{JWT}{JSON Web Token}
|
\acro{JWT}{JSON Web Token}
|
||||||
\end{acronym}
|
\end{acronym}
|
||||||
|
|
|
@ -1,78 +1,35 @@
|
||||||
% !TEX root = Projektdokumentation.tex
|
% !TEX root = Projektdokumentation.tex
|
||||||
\section{Anhang}
|
\section{Anhang}
|
||||||
\subsection{Detaillierte Zeitplanung}
|
|
||||||
\label{app:Zeitplanung}
|
|
||||||
|
|
||||||
\tabelleAnhang{ZeitplanungKomplett}
|
\subsection{Implementierungsbeispiele}
|
||||||
|
\label{app:CodeSchichten}
|
||||||
|
|
||||||
\input{Anhang/AnhangLastenheft.tex}
|
\subsubsection{Frontend-Schicht: Angular Component}
|
||||||
|
\label{app:FrontendComponent}
|
||||||
|
\lstinputlisting[language=C, caption={Angular TypeScript Component - Coinflip Game}]{Listings/CoinflipComponent.ts}
|
||||||
|
|
||||||
\clearpage
|
\clearpage
|
||||||
|
|
||||||
\input{Anhang/AnhangRessourcen.tex}
|
\subsubsection{Controller-Schicht: Spring Boot REST Controller}
|
||||||
|
\label{app:ControllerSchicht}
|
||||||
|
\lstinputlisting[language=java, caption={Spring Boot REST Controller - Coinflip}]{Listings/CoinflipController.java}
|
||||||
|
|
||||||
\clearpage
|
\clearpage
|
||||||
|
|
||||||
\subsection{Use Case-Diagramm}
|
\subsubsection{Service-Schicht: Business Logic}
|
||||||
\label{app:UseCase}
|
\label{app:ServiceSchicht}
|
||||||
\begin{figure}[htb]
|
\lstinputlisting[language=java, caption={Service-Klasse mit Geschäftslogik - Coinflip}]{Listings/CoinflipService.java}
|
||||||
\centering
|
|
||||||
\includegraphicsKeepAspectRatio{UseCase.pdf}{1.3}
|
|
||||||
\caption{Use Case-Diagramm}
|
|
||||||
\end{figure}
|
|
||||||
|
|
||||||
\clearpage
|
\clearpage
|
||||||
|
|
||||||
\subsection{Amortisation}
|
\subsubsection{Persistierung-Schicht: JPA Entity}
|
||||||
\label{app:Amortisation}
|
\label{app:PersistierungSchicht}
|
||||||
Der Zeitpunkt der Amortisation wird als Schnittpunkt der beiden Geraden angegeben.
|
\lstinputlisting[language=java, caption={JPA Entity - Benutzer}]{Listings/UserEntity.java}
|
||||||
|
|
||||||
\begin{figure}[htb]
|
|
||||||
\centering
|
|
||||||
\includegraphicsKeepAspectRatio{amortisationgrafik.png}{1}
|
|
||||||
\caption{Grafische Darstellung der Amortisation}
|
|
||||||
\end{figure}
|
|
||||||
|
|
||||||
\clearpage
|
\clearpage
|
||||||
|
|
||||||
\subsection{composer.json Konfiguration für neusta-m2-intex-client}
|
\subsubsection{Konfiguration: Application Properties}
|
||||||
\label{app:ComposerJson}
|
\label{app:Konfiguration}
|
||||||
\lstinputlisting[language=json, caption={Konfiguration für neusta-m2-intex-client}]{Listings/composer.json}
|
\lstinputlisting[caption={Spring Boot Anwendungskonfiguration}]{Listings/application.properties}
|
||||||
|
|
||||||
\clearpage
|
|
||||||
|
|
||||||
\subsection{Deklaration zur Anlage einer SQL Tabelle im Magento 2 Umfeld}
|
|
||||||
\label{app:InstallData}
|
|
||||||
\lstinputlisting[language=xml, caption={Deklaration zur Anlage einer SQL Tabelle im Magento 2 Umfeld}]{Listings/InstallData.xml}
|
|
||||||
|
|
||||||
\clearpage
|
|
||||||
|
|
||||||
\subsection{Klasse: Factory}
|
|
||||||
\label{app:Factory}
|
|
||||||
\lstinputlisting[language=php, caption={Klasse: Factory}]{Listings/Factory.php}
|
|
||||||
|
|
||||||
\clearpage
|
|
||||||
|
|
||||||
\subsection{Klasse: CustomerConnection}
|
|
||||||
\label{app:CustomerConnection}
|
|
||||||
\lstinputlisting[language=php, caption={Klasse: CustomerConnection}]{Listings/CustomerConnection.php}
|
|
||||||
|
|
||||||
\clearpage
|
|
||||||
|
|
||||||
\subsection{Klasse: Connection}
|
|
||||||
\label{app:Connection}
|
|
||||||
\lstinputlisting[language=php, caption={Abstrakte Klasse: Connection}]{Listings/Connection.php}
|
|
||||||
|
|
||||||
\clearpage
|
|
||||||
|
|
||||||
\subsection{Klasse: CustomerDataController}
|
|
||||||
\label{app:CustomerDataController}
|
|
||||||
\lstinputlisting[language=php, caption={Klasse: CustomerDataController}]{Listings/CustomerDataController.php}
|
|
||||||
|
|
||||||
\clearpage
|
|
||||||
|
|
||||||
\subsection{UnitTest: FactoryTest}
|
|
||||||
\label{app:UnitTest}
|
|
||||||
\lstinputlisting[language=php, caption={Unit Test der Klasse: Factory}]{Listings/UnitTest.php}
|
|
||||||
|
|
||||||
\clearpage
|
\clearpage
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
%PDF-1.5
|
|
||||||
%µí®û
|
|
||||||
3 0 obj
|
|
||||||
<< /Length 4 0 R
|
|
||||||
/Filter /FlateDecode
|
|
||||||
>>
|
|
||||||
stream
|
|
||||||
xœm“MŽ1…÷9EÖ,L;Çਤ™YàþÏŽSÝ
¨¥ê|ñOÞsª~¦’í÷ë=ý^òûïT•–®Ü˜jþ‘™‰kËÐÔ‰<YÔÊ¢6jkæKäèðgG„Jáœî¥]V6$<08>y¨
|
|
||||||
¸»{>Úì:×}}ÉXw„ûôuÑü¬ÿÊî§ö¬<C3B6>f1G–Ýêæ¶ûð‰ÆA<>=HÌMløïÝ›néÁ,4ÍÁ.U’mÏú¦M×Ι´ç<e=Ñ«Œf)M–«Q™ùÕØÿ¬¾¥º\…LÒfÞÑ Œ\ul‹Òh|4aÂ85\A„Ù%ŸÒ ï{ÔBºÆ©ÔŠAËÃûÆë{Â[Ò)}Öë–^
¼}I8kà<6B>XTðBº%=ô„ÁfA®Ö8ÅF b›CS«6{cŒYÌ;áX©ÔÙ7«vGPj7ŠƒÑCo¢_ 'ôôÚ]ÆVw¢L#HlCŸ’ã[ˆ(&45Nàå•÷ñ Š«yF<79><46>t£6ÆoÛA1’ôŠ.Â|ß#üøûp%ßÒ—<>Ò'
|
|
||||||
endstream
|
|
||||||
endobj
|
|
||||||
4 0 obj
|
|
||||||
414
|
|
||||||
endobj
|
|
||||||
2 0 obj
|
|
||||||
<<
|
|
||||||
/ExtGState <<
|
|
||||||
/a0 << /CA 1 /ca 1 >>
|
|
||||||
>>
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
5 0 obj
|
|
||||||
<< /Type /Page
|
|
||||||
/Parent 1 0 R
|
|
||||||
/MediaBox [ 0 0 51.200001 51.200001 ]
|
|
||||||
/Contents 3 0 R
|
|
||||||
/Group <<
|
|
||||||
/Type /Group
|
|
||||||
/S /Transparency
|
|
||||||
/CS /DeviceRGB
|
|
||||||
>>
|
|
||||||
/Resources 2 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
1 0 obj
|
|
||||||
<< /Type /Pages
|
|
||||||
/Kids [ 5 0 R ]
|
|
||||||
/Count 1
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
6 0 obj
|
|
||||||
<< /Creator (cairo 1.10.2 (http://cairographics.org))
|
|
||||||
/Producer (cairo 1.10.2 (http://cairographics.org))
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
7 0 obj
|
|
||||||
<< /Type /Catalog
|
|
||||||
/Pages 1 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
xref
|
|
||||||
0 8
|
|
||||||
0000000000 65535 f
|
|
||||||
0000000812 00000 n
|
|
||||||
0000000528 00000 n
|
|
||||||
0000000015 00000 n
|
|
||||||
0000000506 00000 n
|
|
||||||
0000000600 00000 n
|
|
||||||
0000000877 00000 n
|
|
||||||
0000001004 00000 n
|
|
||||||
trailer
|
|
||||||
<< /Size 8
|
|
||||||
/Root 7 0 R
|
|
||||||
/Info 6 0 R
|
|
||||||
>>
|
|
||||||
startxref
|
|
||||||
1056
|
|
||||||
%%EOF
|
|
|
@ -1,68 +0,0 @@
|
||||||
%PDF-1.5
|
|
||||||
%µí®û
|
|
||||||
3 0 obj
|
|
||||||
<< /Length 4 0 R
|
|
||||||
/Filter /FlateDecode
|
|
||||||
>>
|
|
||||||
stream
|
|
||||||
xœmRIŽÜ0¼ëz#nZž‘'fæ09$ù?<3F>"¥v÷<04>[%’eY¿J«ñü~¯ß~´úþ§ˆÑ²U<C2B2>IêÏÊL,^½Ñ´‰<]ämá N¾f½pDŽ<44>|wD¨5®áÞä +ʆf^ëTp³œù ÙuÙËaÌóHǺ#Ügž›Õ×þ¯ú‘z¤Wë4[(Šl—<6C>}óð#zþqÚ¤¡æ\ä‡÷í<C3B7>îÖf¥
|
|
||||||
v©‘nyÁ[6ºvÎ<ÁxÏGÙO΄àjÃ#Åue7¦³~ö?©o…<6F>lAÆ8,Ö)ÙÕÁŸ• ŒB^X„°í;,<2C>ÌÛ|¹˜fU´(S4¢Fb@=NM,ÖÓ‰a“Àvls‡Ÿ;YA*ó<>Á³ÂeçhøsxÅ¢©÷htö*8.7o hºBŒÏžá4át /jÚ! ü!•i ˜+ ÛŒÉ8µpµC鈪ÿóSSp”´76Ó5ázævñß5otK8%ŽÈÊ~×!âƒÒåËÇuô&‰¦%¡è#V2ÙÆ¾èù?“þRª˜ã“¸a¬3°,Ll»¤h§æ«~5Më_½•ïå/ÅÏÕ
|
|
||||||
endstream
|
|
||||||
endobj
|
|
||||||
4 0 obj
|
|
||||||
447
|
|
||||||
endobj
|
|
||||||
2 0 obj
|
|
||||||
<<
|
|
||||||
/ExtGState <<
|
|
||||||
/a0 << /CA 1 /ca 1 >>
|
|
||||||
>>
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
5 0 obj
|
|
||||||
<< /Type /Page
|
|
||||||
/Parent 1 0 R
|
|
||||||
/MediaBox [ 0 0 51.200001 51.200001 ]
|
|
||||||
/Contents 3 0 R
|
|
||||||
/Group <<
|
|
||||||
/Type /Group
|
|
||||||
/S /Transparency
|
|
||||||
/CS /DeviceRGB
|
|
||||||
>>
|
|
||||||
/Resources 2 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
1 0 obj
|
|
||||||
<< /Type /Pages
|
|
||||||
/Kids [ 5 0 R ]
|
|
||||||
/Count 1
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
6 0 obj
|
|
||||||
<< /Creator (cairo 1.10.2 (http://cairographics.org))
|
|
||||||
/Producer (cairo 1.10.2 (http://cairographics.org))
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
7 0 obj
|
|
||||||
<< /Type /Catalog
|
|
||||||
/Pages 1 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
xref
|
|
||||||
0 8
|
|
||||||
0000000000 65535 f
|
|
||||||
0000000845 00000 n
|
|
||||||
0000000561 00000 n
|
|
||||||
0000000015 00000 n
|
|
||||||
0000000539 00000 n
|
|
||||||
0000000633 00000 n
|
|
||||||
0000000910 00000 n
|
|
||||||
0000001037 00000 n
|
|
||||||
trailer
|
|
||||||
<< /Size 8
|
|
||||||
/Root 7 0 R
|
|
||||||
/Info 6 0 R
|
|
||||||
>>
|
|
||||||
startxref
|
|
||||||
1089
|
|
||||||
%%EOF
|
|
Before Width: | Height: | Size: 65 KiB |
BIN
projektdokumentation/Bilder/component-diagram.drawio.png
Normal file
After Width: | Height: | Size: 261 KiB |
BIN
projektdokumentation/Bilder/container-diagram.drawio.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
projektdokumentation/Bilder/login.png
Executable file
After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 347 KiB |
Before Width: | Height: | Size: 92 KiB |
BIN
projektdokumentation/Bilder/oauth.png
Executable file
After Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 115 KiB |
Before Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 952 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.6 KiB |
|
@ -1,11 +0,0 @@
|
||||||
% !TEX root = ../Projektdokumentation.tex
|
|
||||||
\section{Abnahmephase}
|
|
||||||
\label{sec:Abnahmephase}
|
|
||||||
|
|
||||||
Die Anwendung wurde gemäß der in Abschnitt~\ref{sec:Entwicklungsprozess}: \nameref{sec:Entwicklungsprozess} agilen Entwicklung stetig von Mitarbeitern der \ac{NSD}
|
|
||||||
per Code Review abgenommen. Die Code Review erfolgte über das webbasierte Development Operations Lebenszyklus Tool GitLab \footnote{GitLab - \url{https://gitlab.com}}, in dessen Repository die Anwendung per git \footnote{git - \url{https://git-scm.com/}}
|
|
||||||
versioniert wurde. Sofern ein neues Feature vom Autor fertiggestellt wurde, erstellte der Autor einen sogenannten Merge Request in GitLab. Dieser Merge Request hat zur Folge,
|
|
||||||
dass ein zugeteilter Mitarbeiter der \ac{NSD} den Code freigeben muss und dieser dann erst in den Quellcode der Anwendung übernommen werden kann.
|
|
||||||
|
|
||||||
Teil der Abnahmebedingungen war, dass der zu implementierende Code sinnvoll mit Unit Tests abgedeckt ist. Exemplarisch hierfür steht der \Anhang{app:UnitTest}, welcher
|
|
||||||
einen beispielhaften Unit Test für die Komponente neusta-m2-intex-client-config zeigt.
|
|
|
@ -1,105 +0,0 @@
|
||||||
% !TEX root = ../Projektdokumentation.tex
|
|
||||||
\section{Analysephase}
|
|
||||||
\label{sec:Analysephase}
|
|
||||||
|
|
||||||
|
|
||||||
\subsection{Ist-Analyse}
|
|
||||||
\label{sec:IstAnalyse}
|
|
||||||
Ein Direktimport über Schnittstellen wird für \ac{M2} nicht zur Verfügung gestellt.
|
|
||||||
Derzeit werden solche Importer per Modul für jeden Webshop individuell selbst entwickelt und angepasst.
|
|
||||||
Als Modul bezeichnet man im \ac{M2} Umfeld eine Erweiterung des Frameworks durch neue Funktionalitäten.
|
|
||||||
Die hierfür neu entwickelnden Module stellen \acs{API}-Abfragen an \ac{IX} und importieren die Daten ohne temporäres Zwischenspeichern in \ac{M2}.
|
|
||||||
Vorhandene Fehlerquellen sind hier die benötigte Verbindung zur \ac{IX}-\acs{API} und die unbedingte Richtigkeit der übertragenen Daten.
|
|
||||||
Kommen Fehler beim Import aufgrund von fehlender oder unvollständiger Datensätze zustande, bricht der komplette Import ab.
|
|
||||||
Gleiches gilt für die Export Funktionen zum Exportieren von Bestellungen von \ac{M2} zu \ac{IX}.
|
|
||||||
|
|
||||||
|
|
||||||
\subsection{Wirtschaftlichkeitsanalyse}
|
|
||||||
\label{sec:Wirtschaftlichkeitsanalyse}
|
|
||||||
Aufgrund des in den Abschnitten~\ref{sec:Projektbegruendung}: \nameref{sec:Projektbegruendung} und~\ref{sec:IstAnalyse}: \nameref{sec:IstAnalyse}
|
|
||||||
geschilderten und erläuterten derzeitigen Zustandes von Import/Export-Modulen in Abhängigkeit zum \ac{CRM} \ac{IX} für \ac{M2}, ist die Umsetzung
|
|
||||||
des Projektes unbedingt erforderlich. Eine gerechtfertigte Realisierung aus wirtschaftlichen Gesichtspunkten wird in den folgenden Abschnitten analysiert.
|
|
||||||
|
|
||||||
\subsubsection{\gqq{Make or Buy}-Entscheidung}
|
|
||||||
\label{sec:MakeOrBuyEntscheidung}
|
|
||||||
Bei dem Projekt handelt es sich um die Erfüllung von kundenspezifischen Anforderungen, sofern \ac{IX} als \ac{CRM} genutzt wird.
|
|
||||||
Hierfür lässt sich auf dem Markt keine Lösung finden, die eine Integration der Warenwirtschaft in ein \ac{M2} ermöglicht.
|
|
||||||
Daher soll das Projekt in Eigenentwicklung durchgeführt und durch Nutzung von Open Source Anwendungen erweitert werden.
|
|
||||||
|
|
||||||
|
|
||||||
\subsubsection{Projektkosten}
|
|
||||||
\label{sec:Projektkosten}
|
|
||||||
Im folgenden sollen die Kosten kalkuliert werden, die während der Entwicklung des Projektes anfallen. Neben Personalkosten, die durch die Umsetzung
|
|
||||||
des Projektes verursacht werden, mussten auch Aufwendungen für die in Abschnitt~\ref{sec:Ressourcenplanung}: \nameref{sec:Ressourcenplanung} genannten Ressourcen
|
|
||||||
berücksichtigt werden. Da Personalkosten im Original nicht herausgegeben werden dürfen, erfolgte die Kalkulation anhand von unterstellten Werten.
|
|
||||||
|
|
||||||
Die Durchführungszeit des Projekts beträgt 70 Stunden.
|
|
||||||
Unterstellt wurde ein Stundensatz in Höhe von \eur{10} für Auszubildende sowie \eur{25} für Mitarbeiter.
|
|
||||||
Für die o.g. Ressourcennutzung wurde ein pauschaler Satz von \eur{15} angenommen.
|
|
||||||
|
|
||||||
Eine Aufstellung der Kosten lassen sich der Tabelle~\ref{tab:Kostenaufstellung} entnehmen und betragen insgesamt \eur{2910}.
|
|
||||||
\tabelle{Kostenaufstellung}{tab:Kostenaufstellung}{Kostenaufstellung.tex}
|
|
||||||
|
|
||||||
|
|
||||||
\subsubsection{Amortisationsdauer}
|
|
||||||
\label{sec:Amortisationsdauer}
|
|
||||||
Der folgende Abschnitt dient zur Ermittlung des Zeitpunktes, ab welchem sich die Entwicklung der Anwendung amortisiert hat.
|
|
||||||
Anhand dieses Wertes kann beurteilt werden, ob die Umsetzung des Projektes aus wirtschaftlicher Sicht sinnvoll ist und sich hieraus
|
|
||||||
Kostenvorteile ergeben.
|
|
||||||
|
|
||||||
Nachfolgend soll nun die Amortisationsdauer berechnet werden. Diese gibt an, ab welchem Zeitpunkt die Anschaffungs- und Entwicklungskosten durch die
|
|
||||||
Zeitersparnis beglichen werden kann.
|
|
||||||
|
|
||||||
\tabelle{Zeitersparnis}{tab:Zeitersparnis}{Zeitersparnis} \\
|
|
||||||
|
|
||||||
\textbf{Berechnung der Amortisationsdauer:}
|
|
||||||
\begin{align}
|
|
||||||
415\ \frac{min}{Monat} \cdot 12\ \frac{Monate}{Jahr} = 4980\ \frac{min}{Jahr} = 83\ \frac{h}{Jahr}
|
|
||||||
\end{align}
|
|
||||||
\begin{align}
|
|
||||||
83\ \frac{h}{Jahr} \cdot (\eur{25} + \eur{15}) = 3320\ \frac{\eur{}}{Jahr}
|
|
||||||
\end{align}
|
|
||||||
\begin{align}
|
|
||||||
\frac{\eur{3320}}{2910 \frac{\eur{}}{Jahr}} = 1,14\ Jahre \approx 1\ Jahr\ \&\ 1,5\ Monate
|
|
||||||
\end{align}
|
|
||||||
|
|
||||||
Zusätzlich wurde die Amortisation grafisch dargestellt. Das Diagramm enthält die variablen Kosten (pro Jahr) der neuen Lösung sowie die Kosten,
|
|
||||||
die für die Entwicklung der neuen Umsetzung angefallen sind. Die Grafik findet sich im~\Anhang{app:Amortisation}.
|
|
||||||
|
|
||||||
Anhand der Amortisationsrechnung ergibt sich für das Projekt eine Amortisationsdauer von einem Jahr und ca. eineinhalb Monaten. Dies definiert den Zeitraum, über den
|
|
||||||
die neue Anwendung mindestens eingesetzt werden muss, um Anschaffungskosten und Kosteneinsparung auszugleichen. Da das Projekt von der \ac{NSD} langfristig eingesetzt
|
|
||||||
werden soll, kann das Projekt unter wirtschaftlichen Gesichtspunkten als sinnvoll eingestuft werden.
|
|
||||||
|
|
||||||
\subsection{Nutzwertanalyse}
|
|
||||||
\label{sec:Nutzwertanalyse}
|
|
||||||
Durch die in den Abschnitten~\ref{sec:Projektkosten}: \nameref{sec:Projektkosten} und~\ref{sec:Amortisationsdauer}: \nameref{sec:Amortisationsdauer} aufgeführten Ergebnisse wird die Realisierung
|
|
||||||
des Projektes bereits ausreichend gerechtfertigt. Hierdurch soll an dieser Stelle auf eine detaillierte Analyse nicht-monetärer Vorteile verzichtet werden.
|
|
||||||
Nicht-monetäre Vorteile der Anwendung wären aber \zB die angenehmere Wartbarkeit durch bessere Übersicht der neu implementierten Modulstruktur sowie die Implementierung von aussagekräftigeren Fehlermeldungen
|
|
||||||
und der Möglichkeit, die Anwendung schnell für individuelle Wünsche anzupassen.
|
|
||||||
|
|
||||||
|
|
||||||
\subsection{Anwendungsfälle}
|
|
||||||
\label{sec:Anwendungsfaelle}
|
|
||||||
Eine grobe Übersicht über die von der Anwendung abzudeckenden Anwendungsfälle wurde im Laufe der Analysephase per Use-Case-Diagramm dargestellt.
|
|
||||||
Dieses Diagramm befindet sich im \Anhang{app:UseCase} und bildet alle Funktionen ab, welche aus Anwendersicht benötigt werden.
|
|
||||||
|
|
||||||
Dem Entwickler stehen der Vollimport sowie die Teilimporte der Daten für Nutzer- und Produktdaten zur Verfügung. Die Teilimporte sind erforderlich,
|
|
||||||
um mögliche Fehlimporte im Kleinen neu anstoßen zu können und so einen erneuten Vollimport zu verhindern.
|
|
||||||
|
|
||||||
Beim Auslösen des Import-Vorgangs werden die Daten von der \ac{IX}-\acs{API} angefordert und zwischengespeichert, um abermalige Requests zu verhindern.
|
|
||||||
Die zwischengespeicherten Daten werden dann zu \ac{M2} importiert und dem Webshop zur Verfügung gestellt.
|
|
||||||
|
|
||||||
Der Vorgang des Vollimports wird außerdem auch über einen \acs{CRON}-Job in geplanten Zeitabständen zur Verfügung gestellt, um ein abermaliges, manuelles
|
|
||||||
Anstoßen zu umgehen.
|
|
||||||
|
|
||||||
\subsection{Qualitätsanforderungen}
|
|
||||||
\label{sec:Qualitaetsanforderungen}
|
|
||||||
Die Anwendung soll unter Green Development \footnote{Sustainable Web Design - \url{https://sustainablewebdesign.org/}} Grundsätzen entwickelt werden. Dies bedeutet, dass auf effiziente Datenabfragen sowie auf Minderung von Komplexität während
|
|
||||||
der Entwicklung geachtet wird. Weitere Punkte der Qualitätsanforderungen sind die Möglichkeit nachvollziehbare Logs einzusehen, falls Fehler während
|
|
||||||
eines Importes entstehen sowie die Wartbarkeit der Anwendung durch modulare und entkoppelte Entwicklung. Außerdem soll die Anwendung mit Unit-Tests abgedeckt sein,
|
|
||||||
um die Funktionalität des Codes zu gewährleisten.
|
|
||||||
|
|
||||||
\subsection{Lastenheft/Fachkonzept}
|
|
||||||
\label{sec:Lastenheft}
|
|
||||||
Am Ende der Analysephase wurde mit den Mitarbeitern der \ac{NSD} sowie der \ac{NXP} ein Lastenheft erstellt. Dieses umfasst alle Anforderungen,
|
|
||||||
welche an die neue Anwendung gestellt werden. Ein Auszug aus dem erstellten Lastenheft befindet sich im \Anhang{app:Lastenheft}.
|
|
|
@ -2,8 +2,22 @@
|
||||||
\label{sec: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.
|
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:
|
Authentifizierung läuft über zwei verschiedene Wege ab:
|
||||||
\begin{itemize}
|
\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.
|
||||||
\item \textbf{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.
|
|
||||||
\item \textbf{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.
|
\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.
|
||||||
\item \textbf{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.
|
|
||||||
\end{itemize}
|
\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}
|
|
@ -1,4 +1,3 @@
|
||||||
\clearpage
|
|
||||||
\section{Coinflip}
|
\section{Coinflip}
|
||||||
|
|
||||||
\subsection{Was ist Coinflip?}
|
\subsection{Was ist Coinflip?}
|
||||||
|
@ -36,4 +35,7 @@ Das Spielergebnis wird strukturiert an das Frontend übermittelt und enthält:
|
||||||
\item Gewinnstatus (gewonnen/verloren)
|
\item Gewinnstatus (gewonnen/verloren)
|
||||||
\item Auszahlungsbetrag (bei Gewinn: 2x Einsatz)
|
\item Auszahlungsbetrag (bei Gewinn: 2x Einsatz)
|
||||||
\item Geworfene Münzseite
|
\item Geworfene Münzseite
|
||||||
\end{itemize}
|
\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.
|
|
@ -1,7 +1,5 @@
|
||||||
\section{Deployment}
|
\section{Deployment}
|
||||||
\label{sec:Deployment}
|
\label{sec:Deployment}
|
||||||
Es gibt zwei Server auf denen Instanzen der Applikation laufen.
|
Es gibt zwei Server auf denen Instanzen der Applikation laufen.
|
||||||
\begin{itemize}
|
\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.
|
||||||
\item \textbf{\href{https://casino.simonis.lol/}{Entwicklungsserver}:} Auf dem Entwicklungsserver läuft eine Instanz der Applikation, die für die Entwicklung und das Testen von neuen Features genutzt wird. Diese Instanz ist Lokal bei Constantin gehostet und wird durch einen Cloudflare-Tunnel öffentlich zugänglich gemacht.
|
\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}).
|
||||||
\item \textbf{\href{https://trustworthy.casino/}{Produktionsserver}:} Auf dem Produktionsserver läuft die finale Version der Applikation, die für die Nutzer zugänglich ist. Diese Instanz ist öffentlich zugänglich und wird von den Nutzern genutzt. Diese Instanz ist auf einem gemieteten Server gehostet. Die Applikation wird durch eine Nginx Reverse-Proxy bereitgestellt, die Anfragen an die \acs{API} und das Frontend weiterleitet und SSL-Zertifikate verwaltet.
|
|
||||||
\end{itemize}
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
\clearpage
|
|
||||||
\section{Dice}
|
\section{Dice}
|
||||||
|
|
||||||
\subsection{Was ist Dice?}
|
\subsection{Was ist Dice?}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
% !TEX root = ../Projektdokumentation.tex
|
|
||||||
\section{Dokumentation}
|
|
||||||
\label{sec:Dokumentation}
|
|
||||||
Die Dokumentation der Anwendung besteht aus zwei Bestandteilen: der Projektdokumentation, in welcher der Autor die einzelnen
|
|
||||||
Phasen, die während der Projektumsetzung durchlaufen wurden, beschreibt sowie der Entwicklerdokumentation, bei der es sich um eine
|
|
||||||
detaillierte Beschreibung der Klassen, die von der Anwendung genutzt werden, handelt. Außerdem werden auch deren Attribute und Methoden sowie die
|
|
||||||
Abhängigkeiten der jeweiligen Klassen untereinander erläutert.
|
|
|
@ -1,12 +0,0 @@
|
||||||
% !TEX root = ../Projektdokumentation.tex
|
|
||||||
\section{Einführungsphase}
|
|
||||||
\label{sec:Einfuehrungsphase}
|
|
||||||
|
|
||||||
Durch die in Abschnitt~\ref{sec:ImplementierungGeschaeftslogik}: \nameref{sec:ImplementierungGeschaeftslogik} vorgenommene Implementierung
|
|
||||||
der Anwendung als Composer basierte Packages, ist das zur Verfügung stellen der Anwendung an den \ac{NSD} internen Satis \footnote{Composer Satis - \url{https://github.com/composer/satis}} Server angebunden.
|
|
||||||
Der interne Satis dient als Composer Repository und stellt so eigen entwickelte Module für die \ac{NSD} für alle Kundenprojekte zur Verfügung.
|
|
||||||
|
|
||||||
Die Komponenten der Anwendung werden dort aufgeführt und können in die realen Kundenprojekte nach Fertigstellung der ausgelassenen
|
|
||||||
Funktionen per Composer implementiert werden. Hier kommt nun auch der größte Vorteil zu tragen, da eine Änderung der Module lediglich
|
|
||||||
in der Grundversion, die dem Satis zur Verfügung steht, vorgenommen muss und somit automatisch per Composer bei Deployments für Live Umgebungen übernommen werden.
|
|
||||||
So müssen Änderungen nicht code-seitig an den Projekten vorgenommen werden.
|
|
|
@ -1,55 +1,57 @@
|
||||||
% !TEX root = ../Projektdokumentation.tex
|
% !TEX root = ../Projektdokumentation.tex
|
||||||
\section{Einleitung}
|
\section{Einleitung}
|
||||||
\label{sec:Einleitung}
|
\label{sec:Einleitung}
|
||||||
Die folgende Projektdokumentation behandelt den Ablauf des IHK-Abschlussprojekts, welches der Autor im Rahmen seiner Ausbildung
|
Die folgende Projektdokumentation beschreibt die Entwicklung einer innovativen Online-Casino-Plattform für die HiTec GmbH,
|
||||||
zum Fachinformatiker mit der Fachrichtung Anwendungsentwicklung durchgeführt hat. Ausbildungsbetrieb ist die \ac{NSD},
|
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.
|
||||||
ein Tochterunternehmen der team neusta Unternehmensgruppe mit Sitz in Bremen. Das Kerngeschäft der \ac{NSD} umfasst die Beratung,
|
|
||||||
Entwicklung und Umsetzung von komplexen Software- und \ac{eCommerce}-Lösungen und beschäftigt zur Zeit ca. 1000 Mitarbeiter.
|
|
||||||
|
|
||||||
\subsection{Projektumfeld}
|
\subsection{Projektumfeld}
|
||||||
\label{sec:Projektumfeld}
|
\label{sec:Projektumfeld}
|
||||||
Auftraggeber des Projektes ist die \ac{NXP}, ebenfalls eine Tochtergesellschaft der team neusta Unternehmensgruppe.
|
Auftraggeber des Projektes ist Herr Hofmann, der Inhaber der HiTec GmbH.
|
||||||
Zu den Leistungen der \ac{NXP} gehören die Beratung und Umsetzung der Bereiche Konzeption, Design und Usability sowie die Leitung von Web- und \ac{eCommerce}-Projekten.
|
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.
|
||||||
|
|
||||||
Die technische Umsetzung von Web- und \ac{eCommerce}-Projekten für Kunden der \ac{NXP} wird in Zusammenarbeit mit Mitarbeitern der \ac{NSD} vorgenommen.
|
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.
|
||||||
|
|
||||||
Diese Form der Zusammenarbeit erfordert intensive, regelmäßige Kommunikation und Rücksprache zwischen beiden Parteien.
|
Die technische Umsetzung erfolgt mit den in der HiTec GmbH etablierten Technologien und Projektmanagement-Methoden unter Verwendung agiler Entwicklungsmethoden.
|
||||||
|
|
||||||
\subsection{Projektziel}
|
\subsection{Projektziel}
|
||||||
\label{sec:Projektziel}
|
\label{sec:Projektziel}
|
||||||
Ziel des Projektes ist die Entwicklung einer Schnittstelle zum Importieren und Exportieren von Produkt- und Nutzerdaten
|
Ziel des Projektes ist die Entwicklung einer innovativen Online-Casino-Plattform als neuartiges Produkt für das Portfolio der HiTec GmbH.
|
||||||
zwischen dem \ac{CRM} \ac{IX} und einem \ac{M2} Webshop.
|
|
||||||
|
|
||||||
Die \ac{NSD} entwickelt für Kunden der \ac{NXP} spezifische Webshops und bindet diese an das vom Kunden vorgesehene \ac{CRM} an.
|
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.
|
||||||
Für \ac{IX} erfordert dies eine individuelle Entwicklung pro Kunden, welche mit hohem Aufwand und Kosten verbunden ist.
|
Das System soll eine vollständige Transaktionsabwicklung mit Einzahlungsfunktionen und eine sichere Benutzerauthentifizierung über OAuth2-Integration bieten.
|
||||||
Dieses Projekt soll einen modularen Rahmen für Importe/Exporte zwischen \ac{M2}, einer Open-Source \ac{eCommerce} Platform
|
|
||||||
auf \ac{PHP} Basis und \ac{IX} abbilden, welcher in verschiedene Kundenprojekte implementiert werden kann und so die
|
|
||||||
abermalige Neuentwicklung eines Import/Export Modules ablöst.
|
|
||||||
|
|
||||||
|
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}
|
\subsection{Projektbegründung}
|
||||||
\label{sec:Projektbegruendung}
|
\label{sec:Projektbegruendung}
|
||||||
Die \ac{NSD} entwickelt derzeit für jeden Kunden, der \ac{IX} als \ac{CRM} nutzt, individuell ein Modul zur Erweiterung
|
Der Online-Gaming- und Casino-Markt zeigt kontinuierliches Wachstum und bietet erhebliches Potenzial für innovative Lösungen.
|
||||||
des relevanten \ac{M2}.
|
Durch die Entwicklung einer modernen Casino-Plattform kann die HiTec GmbH eine neue Kundengruppe erschließen und eine Nische im Gaming-Bereich besetzen.
|
||||||
Diese Module handhaben den Kunden- sowie Produktdaten-Import von \ac{IX} zu \ac{M2}. Die Neuentwicklungen für jeden
|
|
||||||
Kunden beinhalten Schwierigkeiten in der Wartung und Fehleranalyse, da jeder Entwickler sich in das individuell entwickelte Modul
|
|
||||||
einarbeiten muss. Gleichzeitig liegt hier kein Standard vor, wie mit Fehlermeldungen, die von der \ac{IX}-\acs{API} empfangen werden, umgegangen wird.
|
|
||||||
Dies resultiert in längerer und umständlicher Wartung und schlägt dem Kunden durch Mehraufwand zu Buche, der vermieden werden kann.
|
|
||||||
|
|
||||||
Ein modularer Rahmen mit einfachen Individualisierungsmöglichkeiten für einen \ac{IX}-Importer als Modul für \ac{M2} sorgt somit für
|
Die Verwendung bewährter Technologien (Java mit Spring-Boot, Angular, Keycloak) minimiert die Entwicklungsrisiken und ermöglicht eine schnelle Markteinführung.
|
||||||
eine Minderung von Neu-Entwicklungskosten und -aufwand. Eine höhere Wartbarkeit mit geringerer Einarbeitungszeit pro Kundenprojekt wird hierdurch
|
Gleichzeitig demonstriert das Projekt die technische Kompetenz des Unternehmens in der Entwicklung komplexer, interaktiver Webanwendungen.
|
||||||
ebenfalls garantiert.
|
|
||||||
|
|
||||||
|
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}
|
\subsection{Projektschnittstellen}
|
||||||
\label{sec:Projektschnittstellen}
|
\label{sec:Projektschnittstellen}
|
||||||
Damit die Daten, die zu \ac{M2} importiert werden sollen, vollständig ermittelt und validiert werden können, muss die Anwendung mit
|
Die Anwendung wird als Full-Stack-Webanwendung mit getrenntem Backend und Frontend entwickelt.
|
||||||
der \ac{IX}-\acs{API} interagieren können. Diese Anforderung soll mit Hilfe von cURL \footnote{cURL - Client URL \url{https://github.com/curl/curl}} im Umfeld der \ac{PHP} Entwicklung umgesetzt werden.
|
Das Backend basiert auf Java mit Spring-Boot und stellt RESTful APIs für die Spiellogik, Benutzerverwaltung und Transaktionsabwicklung bereit.
|
||||||
|
|
||||||
Um den eigentlichen Import-Vorgang der bereits geholten Daten von \ac{IX} starten zu können wird zudem eine Verbindung zur Import-\acs{API} von \ac{M2} benötigt.
|
Für die Benutzerauthentifizierung wird Keycloak als Identity Provider eingesetzt, der OAuth2-Integration mit externen Anbietern wie Google und GitHub ermöglicht.
|
||||||
Aus diesem Grund wird die Anwendung als \ac{M2}-Modul implementiert. So wird garantiert, dass Fehler im Import-Vorgang minimiert werden, weil der Fokus darauf gelegt
|
Die Frontend-Anwendung wird mit Angular entwickelt und kommuniziert über HTTP-APIs mit dem Backend.
|
||||||
wird, die \ac{M2} eigenen Import-Funktionen zu nutzen.
|
|
||||||
|
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}
|
\subsection{Projektabgrenzung}
|
||||||
\label{sec:Projektabgrenzung}
|
\label{sec:Projektabgrenzung}
|
||||||
Da der Projektumfang beschränkt ist, soll die Entwickung der Import- und Export-Funktionen von Bestellungen und Warenkörben nicht Bestandteil dieses Abschlussprojektes sein.
|
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.
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
% !TEX root = ../Projektdokumentation.tex
|
|
||||||
\section{Entwurfsphase}
|
|
||||||
\label{sec:Entwurfsphase}
|
|
||||||
|
|
||||||
\subsection{Zielplattform}
|
|
||||||
\label{sec:Zielplattform}
|
|
||||||
Das Abschlussprojekt soll, wie in Abschnitt~\ref{sec:Projektziel}: \nameref{sec:Projektziel} beschrieben, aufgrund des Aspektes der Fehlerminimierung als \ac{M2}-Modul
|
|
||||||
umgesetzt werden. Zur temporären Zwischenspeicherung der importierten Datensätze von \ac{IX} wird eine Tabelle in das
|
|
||||||
bestehende Datenbank-System der \ac{M2} Instanz implementiert.
|
|
||||||
|
|
||||||
Die Auswahl der Programmiersprache, mit der das Projekt realisiert werden soll, wurde anhand der Anforderungen von \ac{M2}
|
|
||||||
als \ac{eCommerce} Platform durchgeführt. Um eine Fehler minimierende Einbindung in bestehende Shops zu garantieren, wurde beschlossen,
|
|
||||||
auf eine Nutzwertanalyse zu verzichten und den genannten Anforderungen von \ac{M2} Folge zu leisten.
|
|
||||||
|
|
||||||
Daher ist die Programmiersprache \ac{PHP}, unter Berücksichtigung der genannten Gesichtspunkte, am besten geeignet und soll für die Umsetzung des Projektes verwendet werden.
|
|
||||||
|
|
||||||
|
|
||||||
\subsection{Architekturdesign}
|
|
||||||
\label{sec:Architekturdesign}
|
|
||||||
Die Implementierung der einzelnen Import-Funktionen für Benutzer- und Produktdaten sollen auf Basis des Factory Design Pattern \footnote{PHP the Right Way - Factory Pattern - \url{https://phptherightway.com/pages/Design-Patterns.html}}
|
|
||||||
umgesetzt werden. Demnach wird jede Import-Komponente durch eine zugehörige Factory per Singleton Prinzip instanziiert.
|
|
||||||
So wird sichergestellt, dass jeweils nur eine Instanz des Imports vorhanden ist und Rechenlast während der Nutzung der Anwendung
|
|
||||||
minimiert wird.
|
|
||||||
|
|
||||||
Zusätzlich wird jede Funktionalität modular als Composer \footnote{Composer - \url{https://getcomposer.org}} Package eingebunden, sodass die einzelnen Komponenten nur eine Basis-Komponente zum
|
|
||||||
Funktionieren benötigen. Die Basis-Komponente bindet alle gewünschten Komponenten ein und kann sie anhand des o.g. Factory Patterns instanziieren.
|
|
||||||
|
|
||||||
Diese Modularität bildet eine übersichtliche Anwendungsgestaltung, die nachträglich - ohne viel Aufwand - um mehr Funktionalität erweitert werden kann.
|
|
||||||
|
|
||||||
\subsection{Entwurf der Benutzeroberfläche}
|
|
||||||
\label{sec:Benutzeroberflaeche}
|
|
||||||
Der Autor hat sich auf Grund des festgelegten Projektumfangs sowie des festgelegten Nutzerkreises, der aus Mitarbeitern der \ac{NSD} besteht,
|
|
||||||
für eine Umsetzung der Benutzeroberfläche als \ac{CLI}, zur Ausführung der in
|
|
||||||
Abschnitt~\ref{sec:Anwendungsfaelle}: \nameref{sec:Anwendungsfaelle} beschriebenen Anwendungsfälle, entschieden.
|
|
||||||
|
|
||||||
Die Einbindung der Konsolenbefehle soll über die standardisierte \ac{M2} Implementierungsmöglichkeit für \ac{CLI} Befehle erfolgen.
|
|
||||||
|
|
||||||
\subsection{Datenmodell}
|
|
||||||
\label{sec:Datenmodell}
|
|
||||||
|
|
||||||
Um eine Zwischenspeicherung der Daten, die von der \ac{IX}-\ac{API} bereitgestellt werden, zu realisieren, wird eine Tabelle zur
|
|
||||||
Datenstruktur vom betroffenen \ac{M2} Webshop hinzugefügt. Diese Tabelle soll lediglich Informationen über die importierten Daten und den
|
|
||||||
jeweiligen Zeitstempel zur Nachvollziehbarkeit enthalten.
|
|
||||||
|
|
||||||
Da keine komplexen Entitäten hierfür benötigt werden, wurde vom Autor darauf verzichtet ein \ac{ERM} sowie eine Datenbankstruktur zu
|
|
||||||
erstellen.
|
|
||||||
|
|
||||||
|
|
||||||
\subsection{Geschäftslogik}
|
|
||||||
\label{sec:Geschaeftslogik}
|
|
||||||
|
|
||||||
Wie in Abschnitt~\ref{sec:Architekturdesign}: \nameref{sec:Architekturdesign} beschrieben, wird die Anwendung in mehrere Komponenten aufgeteilt.
|
|
||||||
Die Aufteilung der Komponenten erfolgt per Registrierung als Composer Package. Composer als \ac{PHP} Package Manager bietet die Möglichkeit,
|
|
||||||
Abhängigkeiten der genutzten Software zu versionieren und in einem kontrollierten Umfeld zu nutzen. Durch die Aufteilung der Anwendung in solche
|
|
||||||
Packages, erlangt der Autor eine entkoppelte Anwendung, die ohne großen Aufwand ebenfalls erweitert werden kann.
|
|
||||||
|
|
||||||
Es wird eine Basis-Komponente geben von der jede Subkompenente ableitet. Diese Basis-Komponente beinhaltet die Grundabhängigkeiten,
|
|
||||||
damit die Anwendung im jeweiligen \ac{M2} Umfeld lauffähig ist. Diese Grundabhängigkeiten beinhalten die Version der Programmiersprache
|
|
||||||
\ac{PHP}, die Version des genutzten \ac{M2} sowie die genauen Abhängigkeiten der genutzten \ac{M2} Kern-Komponenten. Zusätzlich wird eine Version
|
|
||||||
der Anwendung selbst definiert, sodass in der weiteren Entwicklung eigene Versionen für verschiedene \ac{M2} Versionen (\zB ältere) zur Verfügung gestellt
|
|
||||||
werden könnten. Die Basis Komponente wird den Namen neusta-m2-intex-base tragen.
|
|
||||||
|
|
||||||
neusta-m2-intex-base wird erweitert um folgende Subkomponenten:
|
|
||||||
\begin{itemize}
|
|
||||||
\item neusta-m2-intex-client: Subkomponente zur Verbindung zur \ac{IX} \ac{API}
|
|
||||||
\item neusta-m2-intex-client-config: Subkomponente zur Konfiguration des Key-Value Mappings für Daten von \ac{IX}
|
|
||||||
\item neusta-m2-intex-cli: Subkomponente zur Implementierung der Benutzeroberfläche
|
|
||||||
\item neusta-m2-intex-customer: Subkomponente zur Implementierung der Import Logik für Nutzerdaten
|
|
||||||
\item neusta-m2-intex-catalog: Subkomponente zur Implementierung der Import Logik für Produktdaten
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
Durch die Namenskonvention, die vom Autor gewählt wurde, ist für aussenstehende Entwickler schneller ersichtig, in welcher Komponente
|
|
||||||
welche Logik eingebaut wurde. So wird weiterhin die Wartungsfähigkeit der Anwendung in den Vordergrund gestellt.
|
|
|
@ -1,82 +0,0 @@
|
||||||
% !TEX root = ../Projektdokumentation.tex
|
|
||||||
\section{Implementierungsphase}
|
|
||||||
\label{sec:Implementierungsphase}
|
|
||||||
|
|
||||||
\subsection{Implementierung der Datenstrukturen}
|
|
||||||
\label{sec:ImplementierungDatenstrukturen}
|
|
||||||
Wie in Abschnitt~\ref{sec:Datenmodell}: \nameref{sec:Datenmodell} beschrieben, ermöglicht \ac{M2} eine Einbindung von individuell benötigten \acs{SQL}
|
|
||||||
Tabellen in die vorhandene Datenstruktur.
|
|
||||||
|
|
||||||
Dem \Anhang{app:InstallData} kann ein \acs{XML} entnommen werden, welches der Komponente neusta-m2-intex-base hinzugefügt wurde.
|
|
||||||
Dieses deklarative Initialisieren von Datenstrukturen wird ebenfalls vom Framework Symfony genutzt. Es ermöglicht dem Entwickler anzugeben,
|
|
||||||
wie die Datenstruktur einer Tabelle aussehen soll. \ac{M2} bestimmt dann den Unterschied der derzeitigen Tabellenstruktur und der Struktur, die durch
|
|
||||||
vorhandene \acs{XML} Definitionen angegeben wurde. Diese Unterschiede werden dann durch atomare \acs{SQL} Operationen wiedergespiegelt.
|
|
||||||
Zusätzlich priorisiert \ac{M2} deklarative Schemen und führt diese generell vor allen älteren Schema \ac{PHP} Scripten aus.
|
|
||||||
|
|
||||||
Die bestehende Datenbank wurde durch o.g. vorgehen um eine Basis-Tabelle erweitert, welche zur Nachvollziehbarkeit der Import-Vorgänge beitragen soll.
|
|
||||||
|
|
||||||
\subsection{Implementierung der Benutzeroberfläche}
|
|
||||||
\label{sec:ImplementierungBenutzeroberflaeche}
|
|
||||||
|
|
||||||
Die Implementierung der Benutzeroberfläche erfolgte, wie bereits im Abschnitt~\ref{sec:Benutzeroberflaeche}: \nameref{sec:Benutzeroberflaeche} geplant,
|
|
||||||
mit Hilfe der \ac{M2} eigenen Möglichkeiten zur Einbindung von \ac{CLI}-Befehlen.
|
|
||||||
|
|
||||||
Durch die Entscheidung des Autors, die Anwendung als \ac{M2} Modul umzusetzen, ergibt sich hier der Vorteil, dass die gewünschten und benötigten
|
|
||||||
\ac{CLI} Befehle per Dependency Injection direkt in die \ac{M2} Befehlsliste aufgenommen werden können. Somit können Entwickler bei der Nutzung der Anwendung
|
|
||||||
auf das Standard Interface von \ac{M2} zurückgreifen und separate Zugangsdaten oder Aufrufe werden vermieden.
|
|
||||||
|
|
||||||
Um der \ac{M2} Instanz die gewünschten Befehle zuzufügen, wurde die Klasse CliCommand der Komponente neusta-m2-intex-cli hinzugefügt. Diese Klasse
|
|
||||||
leitet von der abstrakten \ac{M2} Klasse Command ab und bindet in \ac{M2} inkludierte Symfony-Komponenten zur Verarbeitung von Ein- und Ausgabe ein.
|
|
||||||
|
|
||||||
\subsection{Implementierung der Geschäftslogik}
|
|
||||||
\label{sec:ImplementierungGeschaeftslogik}
|
|
||||||
Da die Implementierung der Geschäftslogik den Kernbestandteil des gesamten Projektes darstellt, soll diese im folgenden Abschnitt
|
|
||||||
genauer erläutert werden. Um die Implementierung möglichst komfortabel durchzuführen, wurde die Entwicklungsumgebung JetBrains PHPStorm 2019 hierfür
|
|
||||||
vom Autor gewählt.
|
|
||||||
|
|
||||||
Zu Beginn der Implementierung wurde das Grundgerüst der Komponenten für die Anwendung erstellt. Hierzu wurde für jede in
|
|
||||||
Abschnitt~\ref{sec:Geschaeftslogik}: \nameref{sec:Geschaeftslogik} erwähnte Komponente und Subkomponente die Grundlage zur Einbindung als
|
|
||||||
Composer Package gelegt. Jede Komponente beinhaltet eine \acs{JSON} Datei, die Angaben zum Namen des Packages, der Version, einer Package-Typ Definition
|
|
||||||
(hier: magento2-module), Abhängigkeiten zu anderen Packages sowie Informationen zum Autor beinhaltet (Beispiel: \Anhang{app:ComposerJson}). Somit konnten die Komponenten
|
|
||||||
im einzelnen als separates Projekt angesehen werden.
|
|
||||||
|
|
||||||
Nachdem die Grundlagen zur Aufteilung der Anwendung in modulare Komponenten gelegt wurde, befasste sich der Autor damit, die lokale Entwicklungsumgebung aufzusetzen.
|
|
||||||
Hierzu wurde ein für \ac{M2} bereits vorhandenes Docker \footnote{Docker - \url{https://www.docker.com}} Setup der \ac{NSD} genutzt. Docker ist ein \ac{VM} Container System, welches im genutzten Setup Container für
|
|
||||||
jeweils den Quellcode der \ac{M2} Instanz, der Datenbank sowie dem benötigten NGINX \footnote{NGINX - \url{https://www.nginx.com/}} Server bereit stellt. Dem Autor wurde hiermit ein natives Aufsetzen einer \ac{M2} Instanz
|
|
||||||
erspart, was zur Qualität der Entwicklung erheblich beitrug und gleichzeitig garantiert, dass die Anwendung auf der tatsächlichen Serverumgebung lauffähig ist.
|
|
||||||
|
|
||||||
Nach der Fertigstellung des Grundgerüstes und der lokalen Umgebung konnte mit der eigentlichen Implementierung fortgesetzt werden.
|
|
||||||
|
|
||||||
Wie in Abschnitt~\ref{sec:Entwicklungsprozess}: \nameref{sec:Entwicklungsprozess} beschrieben, entschied der Autor sich für eine agile Umsetzung in einer Mischung
|
|
||||||
aus Kanban und Scrum. Jede einzelne Komponente ergab somit eine Story im Kanban Board, welche nach Scrum Prinzip zusammen mit den Mitarbeitern der \ac{NSD} priorisiert wurden.
|
|
||||||
|
|
||||||
Die Implementierung des Basis Moduls neusta-m2-intex-base wurde zügig Vollzogen, da die Komponente lediglich als Wrapper für alle Funktionen dienen soll.
|
|
||||||
|
|
||||||
Die o.g. Priorisierung gemäß Scrum sah nun vor, dass die Implementierung der Verbindungslogik zu \ac{IX} am wichtigsten ist, da alle weiteren Logik-Komponenten hierrauf aufbauen müssen.
|
|
||||||
Deswegen soll an dieser Stelle die Implementierung der Komponente neusta-m2-intex-client als Beispiel herangezogen werden.
|
|
||||||
|
|
||||||
Zunächst wurde in der \acs{JSON} Datei für Composer definiert, dass neusta-m2-intex-base die Grundabhängigkeit für das Modul darstellt. So wurden duplizierte
|
|
||||||
Einträge in der Konfiguration vermieden, da diese bereits in der Komponente neusta-m2-intex-base inkludiert sind. Zusätzlich besteht eine Abhängigkeit zur Komponente
|
|
||||||
neusta-m2-intex-client-config, da in der Komponente die Konfigurationen für die \ac{IX}-\acs{API} Zugriffe vorgenommen werden sollen. Die Konfigurationen beinhalten neben
|
|
||||||
den Key-Value Paaren zum Mapping der Produkt- und Nutzerattribute auf den \ac{M2} Standard auch die grundlegenden Informationen wie Passwort und Nutzername zur Verbindung
|
|
||||||
mit \ac{IX}.
|
|
||||||
|
|
||||||
Die Aufgabe des neusta-m2-intex-client in der Anwendung soll sein, jegliche Datenverbindung von der entwickelnden Anwendung mit der \ac{IX}-\acs{API}
|
|
||||||
herzustellen und gleichzeitig ein nachvollziehbares Logging dieser Verbindungen und eventuell auftretender Fehler zur Verfügung zu stellen.
|
|
||||||
|
|
||||||
Wie in Abschnitt~\ref{sec:Architekturdesign}: \nameref{sec:Architekturdesign} beschrieben, sah der Entwurf vor, das Factory Design Pattern zu nutzen und so
|
|
||||||
mehrere Instanzen der Verbindungen zu vermeiden. Hierzu wurde in der Komponente neusta-m2-intex-client die Klasse Factory implementiert, welche die Instanziierung
|
|
||||||
der Klassen abbildete, die neusta-m2-intex-client der Anwendung zur Verfügung stellen soll. Der Grundgedanke ist hierbei, dass die Komponente zum Import der Nutzerdaten
|
|
||||||
(hier: neusta-m2-intex-customer) die Factory des neusta-m2-intex-client nutzen und so seine Verbindung zu \ac{IX} herstellen lassen kann. Der Quellcode der Factory kann
|
|
||||||
dem \Anhang{app:Factory} entnommen werden.
|
|
||||||
|
|
||||||
Um Requests zur \ac{IX}-\acs{API} zu verarbeiten, entschied der Autor, den Open Source \ac{HTTP} Client GuzzlePhp \footnote{GuzzlePhp - \url{https://github.com/guzzle/guzzle}} zu nutzen. Hierdurch erlangte der Autor die Möglichkeit,
|
|
||||||
asynchrone Requests an die \acs{API} von \ac{IX} zu stellen, ohne einen eigenen \ac{PHP} \acs{HTTP} Client entwickeln zu müssen.
|
|
||||||
|
|
||||||
Die Factory instanziiert nun die beim Aufruf angeforderte Klasse. Hier als Beispiel CustomerConnection (siehe \Anhang{app:CustomerConnection} und \Anhang{app:Connection}), welche im
|
|
||||||
CustomerDataController in der Komponente neusta-m2-intex-customer instanziiert wird (siehe \Anhang{app:CustomerDataController}).
|
|
||||||
|
|
||||||
Durch den Fokus auf das Fertigstellen der neusta-m2-intex-client Komponente, waren alle folgenden Subkomponenten trivialer zu implementieren, da bei einem Datenimport
|
|
||||||
die Abfrage der Daten der Hauptbestandteil einer solchen Anwendung darstellt. Dem folgend wurde vom Autor in Reihenfolge die Logik zur Verarbeitung der erhaltenen Daten
|
|
||||||
implementiert (neusta-m2-intex-customer \& neusta-m2-intex-catalog) sowie die Möglichkeit die Funktionalität über einen \acs{CRON}-Job anstoßen zu können, welche
|
|
||||||
im \ac{M2} Standard per \acs{XML} Datei inkludiert werden kann.
|
|
|
@ -27,7 +27,7 @@ Services übernehmen die Kommunikation mit dem Backend und kapseln die Geschäft
|
||||||
\subsubsection{Backend-Architektur}
|
\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.
|
Das Backend implementiert eine klassische mehrschichtige Architektur, die eine klare Trennung der Verantwortlichkeiten gewährleistet. Die Controller-Schicht stellt die REST-\acs{API}-Endpunkte bereit und behandelt \acs{HTTP}-Anfragen. Die Service-Schicht enthält die Geschäftslogik und orchestriert verschiedene Use Cases.
|
||||||
|
|
||||||
Die Repository-Schicht abstrahiert den Datenzugriff und verwendet Spring Data JPA für die Kommunikation mit der Datenbank. Entity-Klassen repräsentieren die Domain-Modelle und bilden die Datenbankstrukturen ab.
|
Die Repository-Schicht abstrahiert den Datenzugriff und verwendet Spring Data JPA für die Kommunikation mit der Datenbank. Entity-Klassen repräsentieren die Domain-Modelle und bilden die Datenbankstrukturen ab. Eine detaillierte Darstellung der verschiedenen Architekturschichten mit konkreten Code-Beispielen findet sich im Anhang (siehe \ref{app:CodeSchichten}).
|
||||||
|
|
||||||
\subsection{Datenarchitektur}
|
\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.
|
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.
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
% !TEX root = ../Projektdokumentation.tex
|
|
||||||
\section{Projektplanung}
|
|
||||||
\label{sec:Projektplanung}
|
|
||||||
|
|
||||||
|
|
||||||
\subsection{Projektphasen}
|
|
||||||
\label{sec:Projektphasen}
|
|
||||||
|
|
||||||
Für die Umsetzung des Projektes standen dem Autor 70 Stunden zur Verfügung. Zu Projektbeginn wurden diese auf, wie in der
|
|
||||||
Softwareentwicklung üblich, verschiedene Phasen verteilt, die während der Entwicklung durchlaufen werden.
|
|
||||||
|
|
||||||
Eine grobe Zeitplanung sowie die Hauptphasen lassen sich der Tabelle~\ref{tab:Zeitplanung} entnehmen.
|
|
||||||
Eine Pufferzeit wurde bereits in der Planung der Implementierung inkludiert.
|
|
||||||
Die Hauptphasen werden außerdem noch in kleinere Unterpunkte zerlegt. Eine detaillierte Übersicht der Phasen befindet sich im \Anhang{app:Zeitplanung}.
|
|
||||||
|
|
||||||
\tabelle{Zeitplanung}{tab:Zeitplanung}{ZeitplanungKurz}\\
|
|
||||||
|
|
||||||
\subsection{Ressourcenplanung}
|
|
||||||
\label{sec:Ressourcenplanung}
|
|
||||||
|
|
||||||
Eine genaue Auflistung der verwendeten Ressourcen, welche für das Projekt eingesetzt wurden, kann der Übersicht im
|
|
||||||
\Anhang{app:Ressourcen} entnommen werden. Hiermit sind sowohl Hard- und Softwareressourcen als auch Personal vom Autor berücksichtigt worden.
|
|
||||||
Bei der Auswahl der verwendeten Software wurde darauf geachtet, dass diese kostenfrei (\zB als Open Source) zur Verfügung
|
|
||||||
steht oder die \ac{NSD} bereits Lizenzen für diese besitzt. Hierdurch sollen anfallende Projektkosten so gering wie möglich
|
|
||||||
gehalten werden.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
\subsection{Entwicklungsprozess}
|
|
||||||
\label{sec:Entwicklungsprozess}
|
|
||||||
|
|
||||||
Bevor mit der Realisierung des Projektes begonnen werden konnte, musste sich der Autor für eine geeignete Vorgehensweise zur Umsetzung entscheiden.
|
|
||||||
Der Autor hat sich auf einen agilen Entwicklungsprozess festgelegt. Hierbei wird in Anlehnung
|
|
||||||
an das Vorgehensmodell Kanban \footnote{Wikipedia Kanban Definition - \url{https://en.wikipedia.org/wiki/Kanban}} gearbeitet, welches durch Elemente des Vorgehensmodells Scrum \footnote{Scrum.org - \url{https://www.scrum.org}} erweitert werden soll.
|
|
||||||
Die für Scrum typischen Iterationen der Projektphasen sowie die damit verbundene, stetige Rücksprache
|
|
||||||
mit den Stakeholdern sind einige Merkmale, die diesen agilen Entwicklungsprozess auszeichnen und in diesem Abschlussprojekt Anwendung finden.
|
|
||||||
|
|
||||||
Eine flexible Umsetzung der Anforderung wird durch die relativ kurzen Entwicklungszyklen ermöglicht. Hierdurch können den Mitarbeitern der \ac{NSD}
|
|
||||||
zeitnah Resultate präsentiert werden. Aufgrund dieser Tatsache wurde bei der Projektplanung in Abschnitt~\ref{sec:Projektphasen}: \nameref{sec:Projektphasen} für die Entwurfsphase
|
|
||||||
weniger Zeit eingeplant, da sich Teile dieser Phase erst im Laufe der Entwicklung der Anwendung ergeben.
|
|
||||||
|
|
||||||
Das stetige Feedback zur Anwendung, resultierend aus der engen Kommunikation mit den Mitarbeitern, gewährleistet eine bessere und flexiblere Reaktionsfähigkeit
|
|
||||||
auf Änderungswünsche. So können die Kollegen des Autors während der Entwicklung mit dem System vertraut gemacht werden, welches zur Folge den Vorteil bringt,
|
|
||||||
dass bei Projektabnahme und -einführung Zeit eingespart werden kann. Dies wurde vom Autor in der Zeitkalkulation bereits berücksichtigt.
|
|
||||||
|
|
||||||
Außerdem soll der agile Entwicklungsprozess durch die Praktik der testabgedeckten Entwicklung erweitert werden. Hierbei werden Softwaretests nach der Implementierung
|
|
||||||
des Quellcodes erstellt. Dies hat zum Vorteil, dass nachträgliche Änderungen immer das gleiche Endresultat in der Datenausgabe erwarten und erhöht somit die Wartbarkeit
|
|
||||||
der gesamten Anwendung.
|
|
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
|
@ -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
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,139 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Neusta\IntexClient\Connection\Model;
|
|
||||||
|
|
||||||
|
|
||||||
use GuzzleHttp\Exception\RequestException;
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
use Neusta\IntexClientConfig\Model\Config\Config;
|
|
||||||
use Neusta\IntexClientConfig\Provider\JsonConfigProvider;
|
|
||||||
use \GuzzleHttp\Client as GuzzleHttpClient;
|
|
||||||
use \GuzzleHttp\Event\BeforeEvent;
|
|
||||||
use \GuzzleHttp\Event\CompleteEvent;
|
|
||||||
|
|
||||||
use function sprintf;
|
|
||||||
|
|
||||||
abstract class Connection
|
|
||||||
{
|
|
||||||
protected const HTTP_STATUS_CODE_OK = 200;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var GuzzleHttpClient
|
|
||||||
*/
|
|
||||||
protected $httpClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Config
|
|
||||||
*/
|
|
||||||
protected $serverConfig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var LoggerInterface
|
|
||||||
*/
|
|
||||||
protected $logger;
|
|
||||||
|
|
||||||
public function __construct(JsonConfigProvider $configProvider, LoggerInterface $logger)
|
|
||||||
{
|
|
||||||
$this->logger = $logger;
|
|
||||||
$this->serverConfig = $configProvider->getConfig('server');
|
|
||||||
$this->httpClient = new GuzzleHttpClient(
|
|
||||||
$this->getHttpClientOptions()
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->initHttpEventEmitter();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function authenticate(): bool
|
|
||||||
{
|
|
||||||
$result = false;
|
|
||||||
|
|
||||||
$response = $this->createPost(
|
|
||||||
$this->serverConfig->getAuthUri()
|
|
||||||
);
|
|
||||||
|
|
||||||
$statusCode = $response->getStatusCode();
|
|
||||||
|
|
||||||
if ($statusCode === self::HTTP_STATUS_CODE_OK) {
|
|
||||||
$this->logger->info('authentication successful');
|
|
||||||
$result = true;
|
|
||||||
} else {
|
|
||||||
$this->logger->info(
|
|
||||||
sprintf('authentication failed with Code: %s', $statusCode)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function createPost(string $uri, array $options = []): ?ResponseInterface
|
|
||||||
{
|
|
||||||
$result = null;
|
|
||||||
$request = $this->httpClient->postAsync($uri, $options);
|
|
||||||
|
|
||||||
$request->then(
|
|
||||||
static function (ResponseInterface $response) {
|
|
||||||
$result = $response;
|
|
||||||
},
|
|
||||||
function (RequestException $exception) {
|
|
||||||
$this->logger->info(
|
|
||||||
sprintf('Crucial Error: %s \n Trace: %u', $exception->getMessage(), $exception->getTraceAsString())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function createGet(string $uri, array $options = []): ?ResponseInterface
|
|
||||||
{
|
|
||||||
$result = null;
|
|
||||||
$request = $this->httpClient->getAsync($uri, $options);
|
|
||||||
|
|
||||||
$request->then(
|
|
||||||
static function (ResponseInterface $response) {
|
|
||||||
$result = $response;
|
|
||||||
},
|
|
||||||
function (RequestException $exception) {
|
|
||||||
$this->logger->info(
|
|
||||||
sprintf('Crucial Error: %s \n Trace: %u', $exception->getMessage(), $exception->getTraceAsString())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function initHttpEventEmitter(): void
|
|
||||||
{
|
|
||||||
// initialize the EventEmitters to get a good Logging whether the Request was send
|
|
||||||
// or failed
|
|
||||||
$this->httpClient->getEmitter()->on('before', static function (BeforeEvent $event) {
|
|
||||||
$this->logger->info(
|
|
||||||
sprintf('About to send Request: %s', $event->getRequest())
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->httpClient->getEmitter()->on('complete', static function (CompleteEvent $event) {
|
|
||||||
$this->logger->info(
|
|
||||||
sprintf('Request %s finished', $event->getRequest())
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->httpClient->getEmitter()->on('error', static function (ErrorEvent $event) {
|
|
||||||
$this->logger->info(
|
|
||||||
sprintf('Request %s failed', $event->getRequest())
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getHttpClientOptions(): array
|
|
||||||
{
|
|
||||||
// Configure Base URL for all ongoing Requests and set Cookies to true,
|
|
||||||
// so we can handle the auth cookie over and over again without using authenticate() multiple times
|
|
||||||
return [
|
|
||||||
'base_url' => $this->serverConfig->getBaseUrl(),
|
|
||||||
'cookies' => true
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Neusta\IntexClient\Connection\Model;
|
|
||||||
|
|
||||||
|
|
||||||
use Neusta\IntexClientConfig\Model\Config\Config;
|
|
||||||
use Neusta\IntexClientConfig\Provider\JsonConfigProvider;
|
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
|
|
||||||
use function json_decode;
|
|
||||||
|
|
||||||
class CustomerConnection extends Connection
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var Config
|
|
||||||
*/
|
|
||||||
private $customerConfig;
|
|
||||||
|
|
||||||
public function __construct(JsonConfigProvider $configProvider, LoggerInterface $logger)
|
|
||||||
{
|
|
||||||
parent::__construct($configProvider, $logger);
|
|
||||||
$this->customerConfig = $configProvider->getConfig('customer');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function loadCustomerData(): array
|
|
||||||
{
|
|
||||||
$response = $this->createGet(
|
|
||||||
$this->customerConfig->getCustomerUri()
|
|
||||||
);
|
|
||||||
|
|
||||||
return json_decode($response->getBody(), true);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
|
|
||||||
namespace Neusta\IntexCustomer\Controller;
|
|
||||||
|
|
||||||
|
|
||||||
use Neusta\IntexClient\Connection\Model\CustomerConnection;
|
|
||||||
|
|
||||||
class CustomerDataController
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var CustomerConnection
|
|
||||||
*/
|
|
||||||
private $connection;
|
|
||||||
|
|
||||||
public function __construct(\Neusta\IntexClient\Connection\Factory $connectionFactory, \Neusta\IntexCustomer\Handler\DataHandler $dataHandler)
|
|
||||||
{
|
|
||||||
$this->connection = $connectionFactory->create('customer');
|
|
||||||
$this->customerDataHandler = $dataHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCustomerData(): void
|
|
||||||
{
|
|
||||||
$customerData = $this->connection->loadCustomerData();
|
|
||||||
|
|
||||||
$this->customerDataHandler->saveCustomerData($customerData);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Neusta\IntexClient\Connection;
|
|
||||||
|
|
||||||
use Neusta\IntexClient\Connection\Model\Connection;
|
|
||||||
|
|
||||||
use function class_exists;
|
|
||||||
use function sprintf;
|
|
||||||
use function ucwords;
|
|
||||||
use function strtolower;
|
|
||||||
|
|
||||||
class Factory
|
|
||||||
{
|
|
||||||
public static function create(string $connectionType): Connection
|
|
||||||
{
|
|
||||||
$connectionPath = sprintf(
|
|
||||||
'\Neusta\IntexClient\Model\Connection\%sConnection',
|
|
||||||
ucwords(strtolower($connectionType))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (class_exists($connectionPath)) {
|
|
||||||
return new $connectionPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new \RuntimeException('Invalid Connection Type given');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
<?xml version="1.0" ?>
|
|
||||||
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
|
|
||||||
<table name="neusta_intex_import" resource="default" engine="innodb"
|
|
||||||
comment="Neusta IntexImport Logging Table">
|
|
||||||
<column xsi:type="int" name="value_id" padding="11" unsigned="false" nullable="false" identity="true" comment="Value ID"/>
|
|
||||||
<column xsi:type="smallint" name="attribute_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Attribute ID"/>
|
|
||||||
<column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store ID"/>
|
|
||||||
<column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="Entity ID"/>
|
|
||||||
<column xsi:type="datetime" name="value" on_update="false" nullable="true" comment="Value"/>
|
|
||||||
<constraint xsi:type="primary" referenceId="PRIMARY">
|
|
||||||
<column name="value_id"/>
|
|
||||||
</constraint>
|
|
||||||
<constraint xsi:type="foreign" referenceId="CAT_PRD_ENTT_DTIME_ATTR_ID_EAV_ATTR_ATTR_ID" table="catalog_product_entity_datetime" column="attribute_id" referenceTable="eav_attribute" referenceColumn="attribute_id" onDelete="CASCADE"/>
|
|
||||||
<constraint xsi:type="foreign" referenceId="CAT_PRD_ENTT_DTIME_ENTT_ID_CAT_PRD_ENTT_ENTT_ID" table="catalog_product_entity_datetime" column="entity_id" referenceTable="catalog_product_entity" referenceColumn="entity_id" onDelete="CASCADE"/>
|
|
||||||
<constraint xsi:type="foreign" referenceId="CATALOG_PRODUCT_ENTITY_DATETIME_STORE_ID_STORE_STORE_ID" table="catalog_product_entity_datetime" column="store_id" referenceTable="store" referenceColumn="store_id" onDelete="CASCADE"/>
|
|
||||||
<constraint xsi:type="unique" referenceId="CATALOG_PRODUCT_ENTITY_DATETIME_ENTITY_ID_ATTRIBUTE_ID_STORE_ID">
|
|
||||||
<column name="entity_id"/>
|
|
||||||
<column name="attribute_id"/>
|
|
||||||
<column name="store_id"/>
|
|
||||||
</constraint>
|
|
||||||
<index referenceId="CATALOG_PRODUCT_ENTITY_DATETIME_ATTRIBUTE_ID" indexType="btree">
|
|
||||||
<column name="attribute_id"/>
|
|
||||||
</index>
|
|
||||||
<index referenceId="CATALOG_PRODUCT_ENTITY_DATETIME_STORE_ID" indexType="btree">
|
|
||||||
<column name="store_id"/>
|
|
||||||
</index>
|
|
||||||
</table>
|
|
||||||
</schema>
|
|
|
@ -1,39 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Neusta\IntexClient\Connection\Factory;
|
|
||||||
use Neusta\IntexClient\Connection\Model\CustomerConnection;
|
|
||||||
|
|
||||||
class FactoryTest extends TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var Factory
|
|
||||||
*/
|
|
||||||
private $subject;
|
|
||||||
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
$this->subject = new Factory();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function createMustReturnInstanceOfCustomerConnectionWithValidIdentifierGiven()
|
|
||||||
{
|
|
||||||
$expectedInstance = $this->subject->create('customer');
|
|
||||||
|
|
||||||
self::assertInstanceOf(CustomerConnection::class, $expectedInstance);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function createMustThrowRuntimeExceptionIfInvalidIdentifierIsGiven()
|
|
||||||
{
|
|
||||||
$unexpectedInstance = $this->subject->create('fooBar');
|
|
||||||
|
|
||||||
self::expectException(RuntimeException::class);
|
|
||||||
}
|
|
||||||
}
|
|
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
|
@ -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
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"name": "neusta/m2-intex-client",
|
|
||||||
"license": "proprietary",
|
|
||||||
"description": "Client for Magento2 Intex Import/Export",
|
|
||||||
"type": "magento2-module",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"require": {
|
|
||||||
"php": "~7.1.0|~7.2.0|~7.3.0",
|
|
||||||
"magento/framework": "~101.0",
|
|
||||||
"neusta/m2-intex-base": "0.0.1",
|
|
||||||
"guzzlehttp/guzzle": "6.3.*"
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"files": [
|
|
||||||
"registration.php"
|
|
||||||
],
|
|
||||||
"psr-4": {
|
|
||||||
"Neusta\\IntexClient\\": "src"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
<?php
|
|
||||||
include(dirname(__FILE__).'/../bootstrap/Propel.php');
|
|
||||||
|
|
||||||
$t = new lime_test(13);
|
|
||||||
|
|
||||||
$t->comment('Empty Information');
|
|
||||||
$emptyComparedInformation = new ComparedNaturalModuleInformation(array());
|
|
||||||
$t->is($emptyComparedInformation->getCatalogSign(), ComparedNaturalModuleInformation::EMPTY_SIGN, 'Has no catalog sign');
|
|
||||||
$t->is($emptyComparedInformation->getSourceSign(), ComparedNaturalModuleInformation::SIGN_CREATE, 'Source has to be created');
|
|
||||||
|
|
||||||
$t->comment('Perfect Module');
|
|
||||||
$criteria = new Criteria();
|
|
||||||
$criteria->add(NaturalmodulenamePeer::NAME, 'SMTAB');
|
|
||||||
$moduleName = NaturalmodulenamePeer::doSelectOne($criteria);
|
|
||||||
$t->is($moduleName->getName(), 'SMTAB', 'Right modulename selected');
|
|
||||||
$comparedInformation = $moduleName->loadNaturalModuleInformation();
|
|
||||||
$t->is($comparedInformation->getSourceSign(), ComparedNaturalModuleInformation::SIGN_OK, 'Source sign shines global');
|
|
||||||
$t->is($comparedInformation->getCatalogSign(), ComparedNaturalModuleInformation::SIGN_OK, 'Catalog sign shines global');
|
|
||||||
$infos = $comparedInformation->getNaturalModuleInformations();
|
|
||||||
foreach($infos as $info)
|
|
||||||
{
|
|
||||||
$env = $info->getEnvironmentName();
|
|
||||||
$t->is($info->getSourceSign(), ComparedNaturalModuleInformation::SIGN_OK, 'Source sign shines at ' . $env);
|
|
||||||
if($env != 'SVNENTW')
|
|
||||||
{
|
|
||||||
$t->is($info->getCatalogSign(), ComparedNaturalModuleInformation::SIGN_OK, 'Catalog sign shines at ' . $info->getEnvironmentName());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$t->is($info->getCatalogSign(), ComparedNaturalModuleInformation::EMPTY_SIGN, 'Catalog sign is empty at ' . $info->getEnvironmentName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
|
@ -53,12 +53,6 @@
|
||||||
\pagenumbering{arabic}
|
\pagenumbering{arabic}
|
||||||
\input{Inhalt.tex}
|
\input{Inhalt.tex}
|
||||||
|
|
||||||
% Literatur ------------------------------------------------------------------
|
|
||||||
\clearpage
|
|
||||||
\renewcommand{\refname}{Literaturverzeichnis}
|
|
||||||
\bibliography{Bibliographie}
|
|
||||||
\bibliographystyle{Allgemein/natdin} % DIN-Stil des Literaturverzeichnisses
|
|
||||||
|
|
||||||
% Anhang ---------------------------------------------------------------------
|
% Anhang ---------------------------------------------------------------------
|
||||||
\clearpage
|
\clearpage
|
||||||
\appendix
|
\appendix
|
||||||
|
|
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 19:40
|
||||||
|
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!
|