Compare commits
200 commits
setup/use-
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
cc8f553d66 | ||
|
428c33b000 | ||
|
09ccad479e | ||
|
a933f0b397 | ||
|
b9ce80a28a | ||
8303c906aa | |||
|
219cbfca0e | ||
11c6634d6c | |||
356a599dd5 | |||
0c7c2ae9fa | |||
c75c62b396 | |||
8547bd1fa3 | |||
da047eef70 | |||
da50d19f9d | |||
5cb08ca5f8 | |||
8da1ff8acd | |||
7bd06dee62 | |||
72f56928d4 | |||
6c025cc8d2 | |||
642f5727e5 | |||
8d4901601f | |||
355d1b0c06 | |||
877b6f77b2 | |||
0590a2f9ee | |||
ef8111cc7e | |||
a091387c1c | |||
2392dac519 | |||
fadedb0bcd | |||
59263dca71 | |||
a19ddeed47 | |||
bd26ded681 | |||
5afdbad461 | |||
2326d41a96 | |||
df9fa9f275 | |||
793f3f6834 | |||
c55fcd9ea0 | |||
7533f1139c | |||
6fb3f2bef2 | |||
bb24af241c | |||
c258f1014d | |||
0b61ce11e1 | |||
876d174f8f | |||
c0e7f5f7f8 | |||
c7acd8271a | |||
aaff4a543b | |||
82a3f4d195 | |||
483446cdd9 | |||
562a93bbf1 | |||
840d6b5bfd | |||
36be142de1 | |||
50d0782e02 | |||
4ce4f86419 | |||
313950e998 | |||
aa2bb187ce | |||
c0ad17490e | |||
4c376f8375 | |||
3433641025 | |||
9d8509731f | |||
ba776f0ec1 | |||
f8db9221a6 | |||
0d67c0e305 | |||
586044a23d | |||
a50b9be463 | |||
0d45d9659a | |||
ecdcea4994 | |||
a8a7d4296a | |||
13245cdab6 | |||
9d4fb96daf | |||
1bc2ca5f9a | |||
|
44c7d8be57 | ||
aa39a42df6 | |||
455ebdbe91 | |||
204b205b44 | |||
3cd1f63dba | |||
1c5f5524fa | |||
0868ef0776 | |||
|
695a1073a9 | ||
|
47ca56deb4 | ||
|
0878ed46db | ||
04626e92d1 | |||
9fe473302c | |||
8514d6d73f | |||
84de8e0c86 | |||
b570e8e9ab | |||
8e986727ec | |||
b2f4d9d0d8 | |||
e5bd173be9 | |||
325bc118ee | |||
29305a75b1 | |||
c679952ef9 | |||
70044fc3c7 | |||
21fa5a21e2 | |||
61add61113 | |||
e6f054f10e | |||
ea26f5dd80 | |||
ff0e03003a | |||
7106ebd0ef | |||
5e84afba90 | |||
eede85295e | |||
5c34014841 | |||
313080ceac | |||
|
77b7f822d8 | ||
c8c62b91f3 | |||
cfd1d01f6f | |||
|
7e5424d0cb | ||
dc07ad5b0d | |||
96bd5ac704 | |||
|
9cafe178a4 | ||
5e24e3d020 | |||
4a9c769bbc | |||
|
b0c43e0b7c | ||
|
a0751ae3c6 | ||
d117f5912a | |||
966d99f43c | |||
|
3fc290ecd1 | ||
193f444f4f | |||
c49f7ca55d | |||
7b82445230 | |||
582f8b1a6c | |||
4a4bbbc683 | |||
|
55502b771d | ||
|
c7d62bb4e3 | ||
702e54b5da | |||
b7a60f0c53 | |||
13dfc3cf5b | |||
e2927abe60 | |||
1f49bc6a3d | |||
|
b71858c79d | ||
43db82da8f | |||
|
3bea232dfb | ||
|
372449cd04 | ||
7b020aee75 | |||
cd6ace28f4 | |||
da01e272d7 | |||
|
19ab3933d6 | ||
|
704cc22858 | ||
29d3c53b19 | |||
c4252c97e7 | |||
9a63954e4b | |||
55cd5fefed | |||
6ba4937538 | |||
1d0162d250 | |||
177dd78592 | |||
|
0ef2f58379 | ||
|
ab3bc5587e | ||
|
22086a88fa | ||
|
3a2b92b3ff | ||
9289a72acf | |||
aea34424b8 | |||
21bdbab1cf | |||
9d9399056f | |||
2d955eed1a | |||
dc1482c320 | |||
343704959b | |||
ac3c84106d | |||
3e1e42fa31 | |||
f51d32ba5f | |||
cb80b041a6 | |||
d1aa0222a1 | |||
e270b8abe5 | |||
9dd14cd944 | |||
|
6072e81299 | ||
6a823bf02e | |||
0836830df2 | |||
|
c64152b99f | ||
a958d9f6ac | |||
|
fab3680c07 | ||
9778a1e6d5 | |||
3113eee414 | |||
|
0870f5a73d | ||
|
13769e0df5 | ||
|
4b9574ac96 | ||
|
f6bcc1be11 | ||
|
c3f9ba7bca | ||
|
dc993b2879 | ||
|
53f21a220f | ||
|
f10de66c12 | ||
|
fa30fc3305 | ||
a23a438a7e | |||
94ac9bd491 | |||
f4541c5d86 | |||
774e55c6a6 | |||
25ff804b76 | |||
002ddd6dbe | |||
ab9598950a | |||
f31a959ec5 | |||
|
9a8f9d5c41 | ||
|
7d4cfe1794 | ||
|
b010d49752 | ||
|
f5ae0e358c | ||
|
bbbf0a9473 | ||
|
2cd1c6d8fd | ||
|
35bfa3be7f | ||
7fe8f276cf | |||
f6f52e2724 | |||
099b855d3e | |||
|
c81ad44858 | ||
8fca823e7d | |||
25216d10fa | |||
|
ad2c70b04b |
87 changed files with 4242 additions and 13894 deletions
126
.gitea/workflows/ci.yml
Normal file
126
.gitea/workflows/ci.yml
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
checkstyle:
|
||||||
|
name: "Checkstyle Main"
|
||||||
|
runs-on: "vps-4"
|
||||||
|
container:
|
||||||
|
image: "cimg/openjdk:22.0-node"
|
||||||
|
steps:
|
||||||
|
- name: "Checkout"
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Setup Java 22
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
distribution: "temurin"
|
||||||
|
java-version: "22"
|
||||||
|
- name: "Cache Gradle dependencies"
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.gradle/caches
|
||||||
|
~/.gradle/wrapper
|
||||||
|
key: gradle-${{ runner.os }}-common
|
||||||
|
restore-keys: |
|
||||||
|
gradle-${{ runner.os }}-
|
||||||
|
- name: "Prepare Gradle"
|
||||||
|
working-directory: ./backend
|
||||||
|
run: gradle clean
|
||||||
|
- name: "Check"
|
||||||
|
working-directory: ./backend
|
||||||
|
run: gradle checkstyleMain
|
||||||
|
- name: "Stop Gradle"
|
||||||
|
working-directory: ./backend
|
||||||
|
run: gradle --stop
|
||||||
|
|
||||||
|
eslint:
|
||||||
|
name: eslint
|
||||||
|
runs-on: vps-4
|
||||||
|
container:
|
||||||
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Install bun
|
||||||
|
uses: oven-sh/setup-bun@v2
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
working-directory: ./frontend
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
frontend/node_modules/
|
||||||
|
key: ${{ runner.os }}-bun-
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-bun-
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
cd frontend
|
||||||
|
bun install
|
||||||
|
- name: Run Eslint
|
||||||
|
run: |
|
||||||
|
cd frontend
|
||||||
|
bun run lint
|
||||||
|
|
||||||
|
prettier:
|
||||||
|
name: prettier
|
||||||
|
runs-on: vps-4
|
||||||
|
container:
|
||||||
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Install bun
|
||||||
|
uses: oven-sh/setup-bun@v2
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
working-directory: ./frontend
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
frontend/node_modules/
|
||||||
|
key: ${{ runner.os }}-bun-
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-bun-
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
cd frontend
|
||||||
|
bun install
|
||||||
|
- name: Run prettier
|
||||||
|
run: |
|
||||||
|
cd frontend
|
||||||
|
bun run format:check
|
||||||
|
|
||||||
|
test-build:
|
||||||
|
name: test-build
|
||||||
|
runs-on: vps-4
|
||||||
|
container:
|
||||||
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Install bun
|
||||||
|
uses: oven-sh/setup-bun@v2
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
working-directory: ./frontend
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
frontend/node_modules/
|
||||||
|
key: ${{ runner.os }}-bun-
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-bun-
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
working-directory: ./frontend
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
frontend/dist/
|
||||||
|
key: ${{ runner.os }}-dist-
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-dist-
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
cd frontend
|
||||||
|
bun install
|
||||||
|
- name: Test build
|
||||||
|
run: |
|
||||||
|
cd frontend
|
||||||
|
bun run build
|
27
.gitea/workflows/release.yml
Normal file
27
.gitea/workflows/release.yml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
name: Release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "main"
|
||||||
|
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Release
|
||||||
|
runs-on: vps-4
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
- name: Create Release
|
||||||
|
uses: https://git.kjan.de/actions/semantic-release@main
|
||||||
|
with:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,3 @@
|
||||||
.idea
|
.idea
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.vscode
|
34
README.md
Normal file
34
README.md
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# How to: Semantic Commit Messages
|
||||||
|
|
||||||
|
See how a minor change to your commit message style can make you a better programmer.
|
||||||
|
|
||||||
|
Format: `<type>(<scope>): <subject>`
|
||||||
|
|
||||||
|
`<scope>` is optional
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: add hat wobble
|
||||||
|
^--^ ^------------^
|
||||||
|
| |
|
||||||
|
| +-> Summary in present tense.
|
||||||
|
|
|
||||||
|
+-------> Type: chore, docs, feat, fix, refactor, style, or test.
|
||||||
|
```
|
||||||
|
|
||||||
|
More Examples:
|
||||||
|
|
||||||
|
- `feat`: (new feature for the user, not a new feature for build script)
|
||||||
|
- `fix`: (bug fix for the user, not a fix to a build script)
|
||||||
|
- `docs`: (changes to the documentation)
|
||||||
|
- `style`: (formatting, missing semi colons, etc; no production code change)
|
||||||
|
- `refactor`: (refactoring production code, eg. renaming a variable)
|
||||||
|
- `test`: (adding missing tests, refactoring tests; no production code change)
|
||||||
|
- `chore`: (updating grunt tasks etc; no production code change)
|
||||||
|
|
||||||
|
References:
|
||||||
|
|
||||||
|
- https://www.conventionalcommits.org/
|
||||||
|
- https://seesparkbox.com/foundry/semantic_commit_messages
|
||||||
|
- http://karma-runner.github.io/1.0/dev/git-commit-msg.html
|
20
backend/.docker/Dockerfile
Normal file
20
backend/.docker/Dockerfile
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
FROM gradle:jdk22 AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY gradlew build.gradle.kts settings.gradle.kts ./
|
||||||
|
COPY gradle gradle
|
||||||
|
|
||||||
|
RUN chmod +x gradlew
|
||||||
|
RUN ./gradlew dependencies
|
||||||
|
|
||||||
|
COPY src src
|
||||||
|
|
||||||
|
RUN ./gradlew clean build -x test
|
||||||
|
|
||||||
|
FROM openjdk:22-jdk-slim
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=builder /app/build/libs/*.jar app.jar
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
ENTRYPOINT ["java", "-jar", "app.jar"]
|
2
backend/.dockerignore
Normal file
2
backend/.dockerignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.gradle
|
||||||
|
build
|
|
@ -1,21 +0,0 @@
|
||||||
@token = eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxX3I2eDlta1B3cm9NWHQ5Q1Y4cktyak5WNndybktrWnI0Qk5xYW9QM2VVIn0.eyJleHAiOjE3MjYwNDA4NDQsImlhdCI6MTcyNjA0MDU0NCwianRpIjoiZmIwNWJhNzAtMmFkYy00ZDQyLWJjOWUtMWVmZTE5NjJiMTc2IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo5MDkwL3JlYWxtcy9MRjEyIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjUyY2MwMjA4LWEzYmQtNDM2Ny05NGM1LTA0MDRiMDE2YTAwMyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImxmMTIiLCJzZXNzaW9uX3N0YXRlIjoiMTdlOGRkMjEtMDQ3OS00ZTM1LTgxOTUtOGY5NmFiNWExNjAwIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjQyMDAiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtbGYxMiIsImxmMTJfdGVzdF9yb2xlIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsInNpZCI6IjE3ZThkZDIxLTA0NzktNGUzNS04MTk1LThmOTZhYjVhMTYwMCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoibGYxMl90ZXN0X3VzZXIifQ.gGPUe-0GGFb7yiko-o5yNlPoyTy3kJCEduwd-VHOLzJubAa9CeO_kjgtxYFDQP1xsBR32Wl6eCoSrz6JfAguU3bfuJ4vukKZ-MUWBc5K_It8NgqcdtR9cTs0nTF2qt2sTG99zn_2Cw9Xs0zc-YEkNHX_YnOZ-p0uMFk_YaEtwcjnw8rs00cbNMPLeAZe5C1QD8TNxZZerRgd0GVfs8P4z7exrJjxdVQeTK1jMz8B3uPCUqBRk1rQI3HLlWNfX195Vcituppj7so9mupq7sit8o9g_gxrVKYObZBVMlBDx-YujGA5QDBngiZZNHNyxseZjcbMyMZXsVTenuZbLU6aEA
|
|
||||||
###
|
|
||||||
GET localhost:8080/welcome
|
|
||||||
|
|
||||||
###
|
|
||||||
GET localhost:8080/roles
|
|
||||||
Authorization: Bearer {{token}}
|
|
||||||
|
|
||||||
###
|
|
||||||
GET localhost:8080/hellos
|
|
||||||
Authorization: Bearer {{token}}
|
|
||||||
|
|
||||||
###
|
|
||||||
POST localhost:8080/hellos
|
|
||||||
Authorization: Bearer {{token}}
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"message": "test"
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,6 +2,21 @@ plugins {
|
||||||
java
|
java
|
||||||
id("org.springframework.boot") version "3.3.3"
|
id("org.springframework.boot") version "3.3.3"
|
||||||
id("io.spring.dependency-management") version "1.1.6"
|
id("io.spring.dependency-management") version "1.1.6"
|
||||||
|
id("checkstyle")
|
||||||
|
}
|
||||||
|
|
||||||
|
checkstyle {
|
||||||
|
configFile = file("$rootDir/config/checkstyle/checkstyle.xml")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<Checkstyle> {
|
||||||
|
reports {
|
||||||
|
// Disable HTML report
|
||||||
|
html.required.set(false)
|
||||||
|
|
||||||
|
// Disable XML report
|
||||||
|
xml.required.set(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "de.szut"
|
group = "de.szut"
|
||||||
|
@ -24,6 +39,7 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation("com.stripe:stripe-java:20.79.0")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||||
compileOnly("org.projectlombok:lombok")
|
compileOnly("org.projectlombok:lombok")
|
||||||
|
|
18
backend/config/checkstyle/checkstyle.xml
Normal file
18
backend/config/checkstyle/checkstyle.xml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE module PUBLIC
|
||||||
|
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
||||||
|
"https://checkstyle.org/dtds/configuration_1_3.dtd"
|
||||||
|
>
|
||||||
|
<module name="Checker">
|
||||||
|
<property name="severity" value="error"/>
|
||||||
|
<property name="tabWidth" value="4"/>
|
||||||
|
|
||||||
|
<module name="LineLength">
|
||||||
|
<property name="max" value="500"/>
|
||||||
|
</module>
|
||||||
|
<module name="FileTabCharacter">
|
||||||
|
<property name="eachLine" value="true"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="NewlineAtEndOfFile"/>
|
||||||
|
</module>
|
|
@ -3,4 +3,4 @@ Content-Type: application/x-www-form-urlencoded
|
||||||
|
|
||||||
grant_type=password&client_id=lf12&username=lf12_test_user&password=secret
|
grant_type=password&client_id=lf12&username=lf12_test_user&password=secret
|
||||||
|
|
||||||
|
> {% client.global.set("token", response.body.access_token); %}
|
1
backend/requests/healthCheck.http
Normal file
1
backend/requests/healthCheck.http
Normal file
|
@ -0,0 +1 @@
|
||||||
|
GET localhost:8080/health
|
|
@ -1 +1 @@
|
||||||
rootProject.name = "lf12_starter_backend"
|
rootProject.name = "casino"
|
||||||
|
|
19
backend/src/main/java/de/szut/casino/CasinoApplication.java
Normal file
19
backend/src/main/java/de/szut/casino/CasinoApplication.java
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package de.szut.casino;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class CasinoApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(CasinoApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public static RestTemplate restTemplate() {
|
||||||
|
return new RestTemplate();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package de.szut.lf8_starter.config;
|
package de.szut.casino.config;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,4 +58,4 @@ public class OpenAPIConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package de.szut.casino.deposit;
|
||||||
|
|
||||||
|
import com.stripe.Stripe;
|
||||||
|
import com.stripe.exception.StripeException;
|
||||||
|
import com.stripe.model.checkout.Session;
|
||||||
|
import com.stripe.param.InvoiceItemCreateParams;
|
||||||
|
import com.stripe.param.PriceCreateParams;
|
||||||
|
import com.stripe.param.checkout.SessionCreateParams;
|
||||||
|
import de.szut.casino.deposit.dto.AmountDto;
|
||||||
|
import de.szut.casino.deposit.dto.SessionIdDto;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.autoconfigure.cassandra.CassandraProperties;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
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.RequestHeader;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class DepositController {
|
||||||
|
|
||||||
|
@Value("${stripe.secret.key}")
|
||||||
|
private String stripeKey;
|
||||||
|
|
||||||
|
@PostMapping("/deposit/checkout")
|
||||||
|
public ResponseEntity<SessionIdDto> checkout(
|
||||||
|
@RequestBody @Valid AmountDto amountDto,
|
||||||
|
@RequestHeader("Origin") String origin
|
||||||
|
) throws StripeException {
|
||||||
|
Stripe.apiKey = stripeKey;
|
||||||
|
|
||||||
|
SessionCreateParams params = SessionCreateParams.builder()
|
||||||
|
.addLineItem(SessionCreateParams.LineItem.builder()
|
||||||
|
.setAmount((long) amountDto.getAmount() * 100)
|
||||||
|
.setCurrency("EUR")
|
||||||
|
.setQuantity(1L)
|
||||||
|
.setName("Einzahlung")
|
||||||
|
.build())
|
||||||
|
.setSuccessUrl(origin+"/deposit/success")
|
||||||
|
.setMode(SessionCreateParams.Mode.PAYMENT)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Session session = Session.create(params);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(new SessionIdDto(session.getId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package de.szut.casino.deposit.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class AmountDto {
|
||||||
|
@Min(50)
|
||||||
|
private double amount;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package de.szut.casino.deposit.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class SessionIdDto {
|
||||||
|
private String sessionId;
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package de.szut.lf8_starter.exceptionHandling;
|
package de.szut.casino.exceptionHandling;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
|
@ -1,4 +1,4 @@
|
||||||
package de.szut.lf8_starter.exceptionHandling;
|
package de.szut.casino.exceptionHandling;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
|
@ -1,4 +1,4 @@
|
||||||
package de.szut.lf8_starter.exceptionHandling;
|
package de.szut.casino.exceptionHandling;
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
|
@ -0,0 +1,15 @@
|
||||||
|
package de.szut.casino.health;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class HealthController {
|
||||||
|
|
||||||
|
@GetMapping("/health")
|
||||||
|
public Map<String, String> healthCheck() {
|
||||||
|
return Map.of("status", "UP");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
package de.szut.lf8_starter.security;
|
package de.szut.casino.security;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
|
@ -9,9 +11,6 @@ import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class KeycloakLogoutHandler implements LogoutHandler {
|
public class KeycloakLogoutHandler implements LogoutHandler {
|
||||||
|
@ -46,4 +45,4 @@ public class KeycloakLogoutHandler implements LogoutHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,29 +1,25 @@
|
||||||
package de.szut.lf8_starter.security;
|
package de.szut.casino.security;
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.security.config.Customizer;
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
|
||||||
import org.springframework.security.core.session.SessionRegistry;
|
import org.springframework.security.core.session.SessionRegistry;
|
||||||
import org.springframework.security.core.session.SessionRegistryImpl;
|
import org.springframework.security.core.session.SessionRegistryImpl;
|
||||||
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
|
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
|
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
|
||||||
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
||||||
import org.springframework.security.web.session.HttpSessionEventPublisher;
|
import org.springframework.security.web.session.HttpSessionEventPublisher;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
class KeycloakSecurityConfig {
|
class KeycloakSecurityConfig {
|
||||||
|
@ -56,23 +52,12 @@ class KeycloakSecurityConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
http.authorizeHttpRequests(auth -> auth
|
http.authorizeHttpRequests(auth -> auth
|
||||||
.requestMatchers(new AntPathRequestMatcher("/welcome"))
|
.requestMatchers("/swagger", "/swagger-ui/**", "/v3/api-docs/**", "/health").permitAll()
|
||||||
.permitAll()
|
.anyRequest().authenticated()
|
||||||
.requestMatchers(
|
)
|
||||||
new AntPathRequestMatcher("/swagger"),
|
.oauth2ResourceServer(spec -> spec.jwt(Customizer.withDefaults()));
|
||||||
new AntPathRequestMatcher("/swagger-ui/**"),
|
|
||||||
new AntPathRequestMatcher("/v3/api-docs/**"))
|
|
||||||
.permitAll()
|
|
||||||
.requestMatchers(new AntPathRequestMatcher("/hello/**"))
|
|
||||||
.hasRole("lf12_test_role")
|
|
||||||
.requestMatchers(new AntPathRequestMatcher("/roles"))
|
|
||||||
.authenticated()
|
|
||||||
.requestMatchers(new AntPathRequestMatcher("/"))
|
|
||||||
.permitAll()
|
|
||||||
.anyRequest()
|
|
||||||
.authenticated()).oauth2ResourceServer(spec -> spec.jwt(Customizer.withDefaults()));
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,4 +79,4 @@ class KeycloakSecurityConfig {
|
||||||
});
|
});
|
||||||
return jwtAuthenticationConverter;
|
return jwtAuthenticationConverter;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package de.szut.casino.user;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestHeader;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import de.szut.casino.user.dto.CreateUserDto;
|
||||||
|
import de.szut.casino.user.dto.GetUserDto;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
public class UserController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
@GetMapping("/user/{id}")
|
||||||
|
public ResponseEntity<?> getUser(@PathVariable String id) {
|
||||||
|
if (id == null || !userService.exists(id)) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(userService.getUser(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/user")
|
||||||
|
public ResponseEntity<?> createUser(@RequestBody @Valid CreateUserDto userData) {
|
||||||
|
if (userService.exists(userData.getKeycloakId())) {
|
||||||
|
|
||||||
|
return this.redirect("/user/" + userData.getKeycloakId());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(userService.createUser(userData));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/user")
|
||||||
|
public ResponseEntity<GetUserDto> getCurrentUser(@RequestHeader("Authorization") String token) {
|
||||||
|
GetUserDto userData = userService.getCurrentUser(token);
|
||||||
|
|
||||||
|
if (userData == null) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(userData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseEntity<Object> redirect(String route) {
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.add("Location", route);
|
||||||
|
|
||||||
|
return new ResponseEntity<>(headers, HttpStatus.FOUND);
|
||||||
|
}
|
||||||
|
}
|
30
backend/src/main/java/de/szut/casino/user/UserEntity.java
Normal file
30
backend/src/main/java/de/szut/casino/user/UserEntity.java
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package de.szut.casino.user;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
@Entity
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class UserEntity {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private Long id;
|
||||||
|
@Column(unique = true)
|
||||||
|
private String keycloakId;
|
||||||
|
private String username;
|
||||||
|
private float balance;
|
||||||
|
|
||||||
|
public UserEntity(String keycloakId, String username, float balance) {
|
||||||
|
this.keycloakId = keycloakId;
|
||||||
|
this.username = username;
|
||||||
|
this.balance = balance;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package de.szut.casino.user;
|
||||||
|
|
||||||
|
import de.szut.casino.user.dto.CreateUserDto;
|
||||||
|
import de.szut.casino.user.dto.GetUserDto;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UserMappingService {
|
||||||
|
public GetUserDto mapToGetUserDto(UserEntity user) {
|
||||||
|
return new GetUserDto(user.getKeycloakId(), user.getUsername(), user.getBalance());
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserEntity mapToUserEntity(CreateUserDto createUserDto) {
|
||||||
|
return new UserEntity(createUserDto.getKeycloakId(), createUserDto.getUsername(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package de.szut.casino.user;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public interface UserRepository extends JpaRepository<UserEntity, Long> {
|
||||||
|
@Query("SELECT u FROM UserEntity u WHERE u.keycloakId = ?1")
|
||||||
|
Optional<UserEntity> findOneByKeycloakId(String keycloakId);
|
||||||
|
|
||||||
|
boolean existsByKeycloakId(String keycloakId);
|
||||||
|
}
|
64
backend/src/main/java/de/szut/casino/user/UserService.java
Normal file
64
backend/src/main/java/de/szut/casino/user/UserService.java
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package de.szut.casino.user;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import de.szut.casino.user.dto.CreateUserDto;
|
||||||
|
import de.szut.casino.user.dto.GetUserDto;
|
||||||
|
import de.szut.casino.user.dto.KeycloakUserDto;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UserService {
|
||||||
|
@Autowired
|
||||||
|
private UserRepository userRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RestTemplate http;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserMappingService mappingService;
|
||||||
|
|
||||||
|
public UserEntity createUser(CreateUserDto createUserDto) {
|
||||||
|
UserEntity user = mappingService.mapToUserEntity(createUserDto);
|
||||||
|
userRepository.save(user);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetUserDto getUser(String keycloakId) {
|
||||||
|
Optional<UserEntity> user = this.userRepository.findOneByKeycloakId(keycloakId);
|
||||||
|
|
||||||
|
return user.map(userEntity -> mappingService.mapToGetUserDto(userEntity)).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetUserDto getCurrentUser(String token) {
|
||||||
|
KeycloakUserDto userData = getKeycloakUserInfo(token);
|
||||||
|
|
||||||
|
if (userData == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Optional<UserEntity> user = this.userRepository.findOneByKeycloakId(userData.getSub());
|
||||||
|
|
||||||
|
return user.map(userEntity -> mappingService.mapToGetUserDto(userEntity)).orElse(null);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeycloakUserDto getKeycloakUserInfo(String token) {
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.set("Authorization", token);
|
||||||
|
ResponseEntity<KeycloakUserDto> response = this.http.exchange("http://localhost:9090/realms/LF12/protocol/openid-connect/userinfo", HttpMethod.GET, new HttpEntity<>(headers), KeycloakUserDto.class);
|
||||||
|
|
||||||
|
return response.getBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean exists(String keycloakId) {
|
||||||
|
return userRepository.existsByKeycloakId(keycloakId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package de.szut.casino.user.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class CreateUserDto {
|
||||||
|
private String keycloakId;
|
||||||
|
private String username;
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package de.szut.casino.user.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class GetUserDto {
|
||||||
|
private String keycloakId;
|
||||||
|
private String username;
|
||||||
|
private float balance;
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package de.szut.casino.user.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class KeycloakUserDto {
|
||||||
|
private String sub;
|
||||||
|
private String preferred_username;
|
||||||
|
}
|
|
@ -1,13 +0,0 @@
|
||||||
package de.szut.lf8_starter;
|
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
||||||
|
|
||||||
@SpringBootApplication
|
|
||||||
public class Lf12StarterApplication {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(Lf12StarterApplication.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package de.szut.lf8_starter.config;
|
|
||||||
|
|
||||||
|
|
||||||
import de.szut.lf8_starter.hello.HelloEntity;
|
|
||||||
import de.szut.lf8_starter.hello.HelloRepository;
|
|
||||||
import org.springframework.boot.ApplicationArguments;
|
|
||||||
import org.springframework.boot.ApplicationRunner;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class SampleDataCreator implements ApplicationRunner {
|
|
||||||
|
|
||||||
private HelloRepository repository;
|
|
||||||
|
|
||||||
public SampleDataCreator(HelloRepository repository) {
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run(ApplicationArguments args) {
|
|
||||||
repository.save(new HelloEntity("Hallo Welt!"));
|
|
||||||
repository.save(new HelloEntity("Schöner Tag heute"));
|
|
||||||
repository.save(new HelloEntity("FooBar"));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public RestTemplate restTemplate() {
|
|
||||||
return new RestTemplate();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
package de.szut.lf8_starter.hello;
|
|
||||||
|
|
||||||
|
|
||||||
import de.szut.lf8_starter.exceptionHandling.ResourceNotFoundException;
|
|
||||||
import de.szut.lf8_starter.hello.dto.HelloCreateDto;
|
|
||||||
import de.szut.lf8_starter.hello.dto.HelloGetDto;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
|
||||||
import jakarta.validation.Valid;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping(value = "hellos")
|
|
||||||
public class HelloController {
|
|
||||||
private final HelloService service;
|
|
||||||
private final HelloMapper helloMapper;
|
|
||||||
|
|
||||||
public HelloController(HelloService service, HelloMapper mappingService) {
|
|
||||||
this.service = service;
|
|
||||||
this.helloMapper = mappingService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "creates a new hello with its id and message")
|
|
||||||
@ApiResponses(value = {
|
|
||||||
@ApiResponse(responseCode = "201", description = "created hello",
|
|
||||||
content = {@Content(mediaType = "application/json",
|
|
||||||
schema = @Schema(implementation = HelloGetDto.class))}),
|
|
||||||
@ApiResponse(responseCode = "400", description = "invalid JSON posted",
|
|
||||||
content = @Content),
|
|
||||||
@ApiResponse(responseCode = "401", description = "not authorized",
|
|
||||||
content = @Content)})
|
|
||||||
@PostMapping
|
|
||||||
public HelloGetDto create(@RequestBody @Valid HelloCreateDto helloCreateDto) {
|
|
||||||
HelloEntity helloEntity = this.helloMapper.mapCreateDtoToEntity(helloCreateDto);
|
|
||||||
helloEntity = this.service.create(helloEntity);
|
|
||||||
return this.helloMapper.mapToGetDto(helloEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "delivers a list of hellos")
|
|
||||||
@ApiResponses(value = {
|
|
||||||
@ApiResponse(responseCode = "200", description = "list of hellos",
|
|
||||||
content = {@Content(mediaType = "application/json",
|
|
||||||
schema = @Schema(implementation = HelloGetDto.class))}),
|
|
||||||
@ApiResponse(responseCode = "401", description = "not authorized",
|
|
||||||
content = @Content)})
|
|
||||||
@GetMapping
|
|
||||||
public List<HelloGetDto> findAll() {
|
|
||||||
return this.service
|
|
||||||
.readAll()
|
|
||||||
.stream()
|
|
||||||
.map(e -> this.helloMapper.mapToGetDto(e))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "deletes a Hello by id")
|
|
||||||
@ApiResponses(value = {
|
|
||||||
@ApiResponse(responseCode = "204", description = "delete successful"),
|
|
||||||
@ApiResponse(responseCode = "401", description = "not authorized",
|
|
||||||
content = @Content),
|
|
||||||
@ApiResponse(responseCode = "404", description = "resource not found",
|
|
||||||
content = @Content)})
|
|
||||||
@DeleteMapping("/{id}")
|
|
||||||
@ResponseStatus(code = HttpStatus.NO_CONTENT)
|
|
||||||
public void deleteHelloById(@PathVariable long id) {
|
|
||||||
var entity = this.service.readById(id);
|
|
||||||
if (entity == null) {
|
|
||||||
throw new ResourceNotFoundException("HelloEntity not found on id = " + id);
|
|
||||||
} else {
|
|
||||||
this.service.delete(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "find hellos by message")
|
|
||||||
@ApiResponses(value = {
|
|
||||||
@ApiResponse(responseCode = "200", description = "List of hellos who have the given message",
|
|
||||||
content = {@Content(mediaType = "application/json",
|
|
||||||
schema = @Schema(implementation = HelloGetDto.class))}),
|
|
||||||
@ApiResponse(responseCode = "404", description = "qualification description does not exist",
|
|
||||||
content = @Content),
|
|
||||||
@ApiResponse(responseCode = "401", description = "not authorized",
|
|
||||||
content = @Content)})
|
|
||||||
@GetMapping("/findByMessage")
|
|
||||||
public List<HelloGetDto> findAllEmployeesByQualification(@RequestParam String message) {
|
|
||||||
return this.service
|
|
||||||
.findByMessage(message)
|
|
||||||
.stream()
|
|
||||||
.map(e -> this.helloMapper.mapToGetDto(e))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package de.szut.lf8_starter.hello;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@Entity
|
|
||||||
@Table(name = "hello")
|
|
||||||
public class HelloEntity {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
private long id;
|
|
||||||
|
|
||||||
private String message;
|
|
||||||
|
|
||||||
public HelloEntity(String message) {
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
package de.szut.lf8_starter.hello;
|
|
||||||
|
|
||||||
|
|
||||||
import de.szut.lf8_starter.hello.dto.HelloCreateDto;
|
|
||||||
import de.szut.lf8_starter.hello.dto.HelloGetDto;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class HelloMapper {
|
|
||||||
|
|
||||||
public HelloGetDto mapToGetDto(HelloEntity entity) {
|
|
||||||
return new HelloGetDto(entity.getId(), entity.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
public HelloEntity mapCreateDtoToEntity(HelloCreateDto dto) {
|
|
||||||
var entity = new HelloEntity();
|
|
||||||
entity.setMessage(dto.getMessage());
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
package de.szut.lf8_starter.hello;
|
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface HelloRepository extends JpaRepository<HelloEntity, Long> {
|
|
||||||
|
|
||||||
|
|
||||||
List<HelloEntity> findByMessage(String message);
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
package de.szut.lf8_starter.hello;
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class HelloService {
|
|
||||||
private final HelloRepository repository;
|
|
||||||
|
|
||||||
public HelloService(HelloRepository repository) {
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HelloEntity create(HelloEntity entity) {
|
|
||||||
return this.repository.save(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<HelloEntity> readAll() {
|
|
||||||
return this.repository.findAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
public HelloEntity readById(long id) {
|
|
||||||
Optional<HelloEntity> optionalQualification = this.repository.findById(id);
|
|
||||||
if (optionalQualification.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return optionalQualification.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void delete(HelloEntity entity) {
|
|
||||||
this.repository.delete(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<HelloEntity> findByMessage(String message) {
|
|
||||||
return this.repository.findByMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package de.szut.lf8_starter.hello.dto;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
|
||||||
import jakarta.validation.constraints.Size;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public class HelloCreateDto {
|
|
||||||
|
|
||||||
@Size(min = 3, message = "at least length of 3")
|
|
||||||
private String message;
|
|
||||||
|
|
||||||
@JsonCreator
|
|
||||||
public HelloCreateDto(String message) {
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package de.szut.lf8_starter.hello.dto;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public class HelloGetDto {
|
|
||||||
|
|
||||||
private long id;
|
|
||||||
|
|
||||||
private String message;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
package de.szut.lf8_starter.welcome;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
public class WelcomeController {
|
|
||||||
|
|
||||||
@GetMapping("/welcome")
|
|
||||||
public String welcome() {
|
|
||||||
return "welcome to lf8_starter";
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/roles")
|
|
||||||
public ResponseEntity<?> getRoles(Authentication authentication) {
|
|
||||||
return ResponseEntity.ok(authentication.getAuthorities());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,9 +1,9 @@
|
||||||
spring.datasource.url=jdbc:postgresql://localhost:5432/postgresdb
|
spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:5432/postgresdb
|
||||||
spring.datasource.username=postgres_user
|
spring.datasource.username=postgres_user
|
||||||
spring.datasource.password=postgres_pass
|
spring.datasource.password=postgres_pass
|
||||||
server.port=8080
|
server.port=8080
|
||||||
spring.jpa.hibernate.ddl-auto=create-drop
|
spring.jpa.hibernate.ddl-auto=create-drop
|
||||||
|
stripe.secret.key=${STRIPE_SECRET_KEY:sk_test_51QrePYIvCfqz7ANgqam8rEwWcMeKiLOof3j6SCMgu2sl4sESP45DJxca16mWcYo1sQaiBv32CMR6Z4AAAGQPCJo300ubuZKO8I}
|
||||||
|
|
||||||
spring.application.name=lf12_starter
|
spring.application.name=lf12_starter
|
||||||
#client registration configuration
|
#client registration configuration
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package de.szut.lf8_starter;
|
package de.szut.casino;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
|
@ -1,5 +1,3 @@
|
||||||
version: '3'
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
keycloak_data:
|
keycloak_data:
|
||||||
postgres_data_keycloak_db:
|
postgres_data_keycloak_db:
|
||||||
|
|
5
frontend/.postcssrc.json
Normal file
5
frontend/.postcssrc.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"plugins": {
|
||||||
|
"@tailwindcss/postcss": {}
|
||||||
|
}
|
||||||
|
}
|
4
frontend/.prettierignore
Normal file
4
frontend/.prettierignore
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
dist
|
||||||
|
coverage
|
||||||
|
node_modules
|
||||||
|
.angular
|
9
frontend/.prettierrc
Normal file
9
frontend/.prettierrc
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"printWidth": 100,
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"endOfLine": "lf"
|
||||||
|
}
|
|
@ -16,9 +16,6 @@
|
||||||
"outputPath": "dist/lf10-starter2024",
|
"outputPath": "dist/lf10-starter2024",
|
||||||
"index": "src/index.html",
|
"index": "src/index.html",
|
||||||
"browser": "src/main.ts",
|
"browser": "src/main.ts",
|
||||||
"polyfills": [
|
|
||||||
"zone.js"
|
|
||||||
],
|
|
||||||
"tsConfig": "tsconfig.app.json",
|
"tsConfig": "tsconfig.app.json",
|
||||||
"assets": [
|
"assets": [
|
||||||
{
|
{
|
||||||
|
@ -73,10 +70,6 @@
|
||||||
"test": {
|
"test": {
|
||||||
"builder": "@angular-devkit/build-angular:karma",
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
"options": {
|
"options": {
|
||||||
"polyfills": [
|
|
||||||
"zone.js",
|
|
||||||
"zone.js/testing"
|
|
||||||
],
|
|
||||||
"tsConfig": "tsconfig.spec.json",
|
"tsConfig": "tsconfig.spec.json",
|
||||||
"assets": [
|
"assets": [
|
||||||
{
|
{
|
||||||
|
@ -89,11 +82,24 @@
|
||||||
],
|
],
|
||||||
"scripts": []
|
"scripts": []
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"builder": "@angular-eslint/builder:lint",
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.html"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cli": {
|
"cli": {
|
||||||
"analytics": "33c8483f-3876-4eb5-9c9b-1001cab9b273"
|
"analytics": "33c8483f-3876-4eb5-9c9b-1001cab9b273",
|
||||||
|
"packageManager": "bun",
|
||||||
|
"schematicCollections": [
|
||||||
|
"angular-eslint"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2454
frontend/bun.lock
Normal file
2454
frontend/bun.lock
Normal file
File diff suppressed because it is too large
Load diff
43
frontend/eslint.config.js
Normal file
43
frontend/eslint.config.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// @ts-check
|
||||||
|
const eslint = require("@eslint/js");
|
||||||
|
const tseslint = require("typescript-eslint");
|
||||||
|
const angular = require("angular-eslint");
|
||||||
|
|
||||||
|
module.exports = tseslint.config(
|
||||||
|
{
|
||||||
|
files: ["**/*.ts"],
|
||||||
|
extends: [
|
||||||
|
eslint.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
...tseslint.configs.stylistic,
|
||||||
|
...angular.configs.tsRecommended,
|
||||||
|
],
|
||||||
|
processor: angular.processInlineTemplates,
|
||||||
|
rules: {
|
||||||
|
"@angular-eslint/directive-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
type: "attribute",
|
||||||
|
prefix: "app",
|
||||||
|
style: "camelCase",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@angular-eslint/component-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
prefix: "app",
|
||||||
|
style: "kebab-case",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["**/*.html"],
|
||||||
|
extends: [
|
||||||
|
...angular.configs.templateRecommended,
|
||||||
|
...angular.configs.templateAccessibility,
|
||||||
|
],
|
||||||
|
rules: {},
|
||||||
|
}
|
||||||
|
);
|
13383
frontend/package-lock.json
generated
13383
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -2,39 +2,55 @@
|
||||||
"name": "lf10-starter2024",
|
"name": "lf10-starter2024",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "bunx @angular/cli",
|
||||||
"start": "ng serve --proxy-config src/proxy.conf.json",
|
"start": "bunx @angular/cli serve --proxy-config src/proxy.conf.json",
|
||||||
"build": "ng build",
|
"build": "bunx @angular/cli build",
|
||||||
"watch": "ng build --watch --configuration development",
|
"watch": "bunx @angular/cli build --watch --configuration development",
|
||||||
"test": "ng test"
|
"test": "bunx @angular/cli test",
|
||||||
|
"format": "prettier --write \"src/**/*.{ts,html,css,scss}\"",
|
||||||
|
"format:check": "prettier --check \"src/**/*.{ts,html,css,scss}\"",
|
||||||
|
"lint": "ng lint"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^18.2.0",
|
"@angular/animations": "^18.2.0",
|
||||||
|
"@angular/cdk": "~18.2.14",
|
||||||
"@angular/common": "^18.2.0",
|
"@angular/common": "^18.2.0",
|
||||||
"@angular/compiler": "^18.2.0",
|
"@angular/compiler": "^18.2.0",
|
||||||
"@angular/core": "^18.2.0",
|
"@angular/core": "^18.2.0",
|
||||||
"@angular/forms": "^18.2.0",
|
"@angular/forms": "^18.2.0",
|
||||||
|
"@angular/material": "~18.2.14",
|
||||||
"@angular/platform-browser": "^18.2.0",
|
"@angular/platform-browser": "^18.2.0",
|
||||||
"@angular/platform-browser-dynamic": "^18.2.0",
|
"@angular/platform-browser-dynamic": "^18.2.0",
|
||||||
"@angular/router": "^18.2.0",
|
"@angular/router": "^18.2.0",
|
||||||
|
"@fortawesome/angular-fontawesome": "^1.0.0",
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||||
|
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||||
|
"@stripe/stripe-js": "^5.6.0",
|
||||||
|
"@tailwindcss/postcss": "^4.0.3",
|
||||||
"keycloak-angular": "^16.0.1",
|
"keycloak-angular": "^16.0.1",
|
||||||
"keycloak-js": "^25.0.5",
|
"keycloak-js": "^25.0.5",
|
||||||
|
"postcss": "^8.5.1",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tailwindcss": "^4.0.3",
|
||||||
"zone.js": "~0.14.10"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "^18.2.2",
|
"@angular-devkit/build-angular": "^18.2.2",
|
||||||
"@angular/cli": "^18.2.2",
|
"@angular/cli": "^18.2.2",
|
||||||
"@angular/compiler-cli": "^18.2.0",
|
"@angular/compiler-cli": "^18.2.0",
|
||||||
"@types/jasmine": "~5.1.0",
|
"@types/jasmine": "~5.1.0",
|
||||||
|
"angular-eslint": "19.1.0",
|
||||||
|
"eslint": "^9.20.0",
|
||||||
"jasmine-core": "~5.2.0",
|
"jasmine-core": "~5.2.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",
|
||||||
"karma-jasmine": "~5.1.0",
|
"karma-jasmine": "~5.1.0",
|
||||||
"karma-jasmine-html-reporter": "~2.1.0",
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
"typescript": "~5.5.2"
|
"prettier": "^3.4.2",
|
||||||
|
"typescript": "~5.5.2",
|
||||||
|
"typescript-eslint": "8.23.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
BIN
frontend/public/blackjack.webp
Normal file
BIN
frontend/public/blackjack.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 83 KiB |
BIN
frontend/public/liars-dice.webp
Normal file
BIN
frontend/public/liars-dice.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
BIN
frontend/public/lootbox.webp
Normal file
BIN
frontend/public/lootbox.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 167 KiB |
BIN
frontend/public/plinko.webp
Normal file
BIN
frontend/public/plinko.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
frontend/public/poker.webp
Normal file
BIN
frontend/public/poker.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
BIN
frontend/public/slots.webp
Normal file
BIN
frontend/public/slots.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 186 KiB |
|
@ -1,6 +0,0 @@
|
||||||
export class Hello {
|
|
||||||
constructor(public id?: number,
|
|
||||||
public message?: string,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,6 @@
|
||||||
<h1>LF12 Starter</h1>
|
<div class="min-h-screen flex flex-col">
|
||||||
|
<main class="flex-grow">
|
||||||
<a href="/hello">hello</a>
|
<router-outlet></router-outlet>
|
||||||
|
</main>
|
||||||
<router-outlet></router-outlet>
|
<app-footer></app-footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,16 @@
|
||||||
import {APP_INITIALIZER, Component} from '@angular/core';
|
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
import {HelloListComponent} from "./hello-list/hello-list.component";
|
import { KeycloakAngularModule } from 'keycloak-angular';
|
||||||
import {KeycloakAngularModule} from "keycloak-angular";
|
import { FooterComponent } from './shared/components/footer/footer.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, HelloListComponent, RouterOutlet, KeycloakAngularModule],
|
imports: [CommonModule, RouterOutlet, KeycloakAngularModule, FooterComponent],
|
||||||
providers: [ ],
|
providers: [],
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrl: './app.component.css'
|
styleUrl: './app.component.css',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AppComponent{
|
export class AppComponent {}
|
||||||
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +1,19 @@
|
||||||
import {APP_INITIALIZER, ApplicationConfig} from '@angular/core';
|
import {
|
||||||
|
APP_INITIALIZER,
|
||||||
|
ApplicationConfig,
|
||||||
|
provideExperimentalZonelessChangeDetection,
|
||||||
|
} from '@angular/core';
|
||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
|
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
||||||
|
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
import {KeycloakAngularModule, KeycloakBearerInterceptor, KeycloakService} from "keycloak-angular";
|
import {
|
||||||
import {HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi} from "@angular/common/http";
|
KeycloakAngularModule,
|
||||||
|
KeycloakBearerInterceptor,
|
||||||
|
KeycloakService,
|
||||||
|
} from 'keycloak-angular';
|
||||||
|
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
||||||
|
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||||
|
|
||||||
export const initializeKeycloak = (keycloak: KeycloakService) => async () =>
|
export const initializeKeycloak = (keycloak: KeycloakService) => async () =>
|
||||||
keycloak.init({
|
keycloak.init({
|
||||||
|
@ -15,34 +25,35 @@ export const initializeKeycloak = (keycloak: KeycloakService) => async () =>
|
||||||
loadUserProfileAtStartUp: true,
|
loadUserProfileAtStartUp: true,
|
||||||
initOptions: {
|
initOptions: {
|
||||||
onLoad: 'check-sso',
|
onLoad: 'check-sso',
|
||||||
silentCheckSsoRedirectUri:
|
silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
|
||||||
window.location.origin + '/silent-check-sso.html',
|
|
||||||
checkLoginIframe: false,
|
checkLoginIframe: false,
|
||||||
redirectUri: 'http://localhost:4200',
|
redirectUri: window.location.origin + '/',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function initializeApp(keycloak: KeycloakService): () => Promise<boolean> {
|
function initializeApp(keycloak: KeycloakService): () => Promise<boolean> {
|
||||||
return () => initializeKeycloak(keycloak)();
|
return () => initializeKeycloak(keycloak)();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [provideRouter(routes),
|
providers: [
|
||||||
KeycloakAngularModule,
|
provideRouter(routes),
|
||||||
{
|
KeycloakAngularModule,
|
||||||
provide: APP_INITIALIZER,
|
FontAwesomeModule,
|
||||||
useFactory: initializeApp,
|
{
|
||||||
multi: true,
|
provide: APP_INITIALIZER,
|
||||||
deps: [KeycloakService]
|
useFactory: initializeApp,
|
||||||
},
|
multi: true,
|
||||||
KeycloakService,
|
deps: [KeycloakService],
|
||||||
provideHttpClient(withInterceptorsFromDi()),
|
},
|
||||||
{
|
KeycloakService,
|
||||||
provide: HTTP_INTERCEPTORS,
|
provideHttpClient(withInterceptorsFromDi()),
|
||||||
useClass: KeycloakBearerInterceptor,
|
provideExperimentalZonelessChangeDetection(),
|
||||||
multi: true
|
{
|
||||||
}
|
provide: HTTP_INTERCEPTORS,
|
||||||
]
|
useClass: KeycloakBearerInterceptor,
|
||||||
};
|
multi: true,
|
||||||
|
},
|
||||||
|
provideAnimationsAsync(),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
|
@ -1,7 +1,19 @@
|
||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
import {HelloListComponent} from "./hello-list/hello-list.component";
|
import { LandingComponent } from './feature/landing/landing.component';
|
||||||
import {authGuard} from "./auth.guard";
|
import { authGuard } from './auth.guard';
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{ path: 'hello', component: HelloListComponent , canActivate: [authGuard] }
|
{
|
||||||
|
path: '',
|
||||||
|
component: LandingComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'login/success',
|
||||||
|
loadComponent: () => import('./feature/login-success/login-success.component'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'home',
|
||||||
|
loadComponent: () => import('./feature/home/home.component'),
|
||||||
|
canActivate: [authGuard],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
|
import { CanActivateFn, Router } from '@angular/router';
|
||||||
import { CanActivateFn } from '@angular/router';
|
|
||||||
import { inject } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {KeycloakService} from "keycloak-angular";
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
|
|
||||||
export const authGuard: CanActivateFn = async (route, state) => {
|
export const authGuard: CanActivateFn = async () => {
|
||||||
const keycloakService = inject(KeycloakService);
|
const keycloakService = inject(KeycloakService);
|
||||||
|
const router = inject(Router);
|
||||||
|
|
||||||
const isLoggedIn = keycloakService.isLoggedIn();
|
if (keycloakService.isLoggedIn()) {
|
||||||
|
|
||||||
if (isLoggedIn) {
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
keycloakService.login();
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
router.navigate(['']);
|
||||||
|
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
21
frontend/src/app/feature/deposit/deposit.component.html
Normal file
21
frontend/src/app/feature/deposit/deposit.component.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<h2 mat-dialog-title class="text-xl font-semibold">Guthaben aufladen</h2>
|
||||||
|
<mat-dialog-content>
|
||||||
|
<form [formGroup]="form">
|
||||||
|
<div *ngIf="errorMsg">
|
||||||
|
{{ errorMsg }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="amount">Betrag</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="amount"
|
||||||
|
formControlName="amount"
|
||||||
|
class="w-full px-2 py-1 bg-white text-black"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</mat-dialog-content>
|
||||||
|
<mat-dialog-actions>
|
||||||
|
<button mat-flat-button (click)="closeDialog()">Abbrechen</button>
|
||||||
|
<button mat-flat-button (click)="submit()">Einzahlen</button>
|
||||||
|
</mat-dialog-actions>
|
69
frontend/src/app/feature/deposit/deposit.component.ts
Normal file
69
frontend/src/app/feature/deposit/deposit.component.ts
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core';
|
||||||
|
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
import { loadStripe, Stripe } from '@stripe/stripe-js';
|
||||||
|
import { DepositService } from '../../service/deposit.service';
|
||||||
|
import { debounceTime } from 'rxjs';
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
|
import { NgIf } from '@angular/common';
|
||||||
|
import {
|
||||||
|
MatDialogActions,
|
||||||
|
MatDialogContent,
|
||||||
|
MatDialogRef,
|
||||||
|
MatDialogTitle,
|
||||||
|
} from '@angular/material/dialog';
|
||||||
|
import { MatButton } from '@angular/material/button';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-deposit',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
ReactiveFormsModule,
|
||||||
|
NgIf,
|
||||||
|
MatDialogTitle,
|
||||||
|
MatDialogContent,
|
||||||
|
MatDialogActions,
|
||||||
|
MatButton,
|
||||||
|
],
|
||||||
|
templateUrl: './deposit.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class DepositComponent implements OnInit {
|
||||||
|
protected form!: FormGroup;
|
||||||
|
protected errorMsg = '';
|
||||||
|
private stripe: Stripe | null = null;
|
||||||
|
private service: DepositService = inject(DepositService);
|
||||||
|
public dialogRef: MatDialogRef<DepositComponent> = inject(MatDialogRef<DepositComponent>);
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
this.form = new FormGroup({
|
||||||
|
amount: new FormControl(50, [Validators.min(50)]),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.form.controls['amount'].valueChanges.pipe(debounceTime(1000)).subscribe((value) => {
|
||||||
|
if (value < 50) {
|
||||||
|
this.errorMsg = 'Minimum Einzahlungsbetrag ist 50€';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.stripe = await loadStripe(environment.STRIPE_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
if (!this.stripe) {
|
||||||
|
this.errorMsg = 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.form.valid) {
|
||||||
|
this.errorMsg = 'Bitte geben Sie einen gültigen Betrag ein.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.service.handleDeposit(this.form.value.amount as number).subscribe(({ sessionId }) => {
|
||||||
|
this.stripe?.redirectToCheckout({ sessionId });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialog(): void {
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
}
|
91
frontend/src/app/feature/home/home.component.html
Normal file
91
frontend/src/app/feature/home/home.component.html
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
<app-navbar></app-navbar>
|
||||||
|
<div class="container mx-auto px-4 py-6 space-y-8">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<div class="flex items-center space-x-4"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
||||||
|
<div class="lg:col-span-3">
|
||||||
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<h3 class="section-heading text-2xl">Beliebte Spiele</h3>
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<button class="nav-button left-0">
|
||||||
|
<span class="material-icons">chevron_left</span>
|
||||||
|
</button>
|
||||||
|
<button class="nav-button right-0">
|
||||||
|
<span class="material-icons">chevron_right</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="slider-container">
|
||||||
|
<div class="slider-grid">
|
||||||
|
<div class="card group" *ngFor="let game of featuredGames">
|
||||||
|
<div class="relative">
|
||||||
|
<img [src]="game.image" [alt]="game.name" class="w-full aspect-[4/3] object-cover" />
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 bg-gradient-to-t from-deep-blue/90 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"
|
||||||
|
>
|
||||||
|
<div class="absolute bottom-4 left-4 right-4">
|
||||||
|
<h4 class="game-heading">{{ game.name }}</h4>
|
||||||
|
<button class="button-base w-full py-2">Jetzt Spielen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8">
|
||||||
|
<h3 class="section-heading text-2xl mb-6">Alle Spiele</h3>
|
||||||
|
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||||
|
<div class="card group" *ngFor="let game of allGames">
|
||||||
|
<div class="relative">
|
||||||
|
<img [src]="game.image" [alt]="game.name" class="w-full aspect-[4/3] object-cover" />
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 bg-gradient-to-t from-deep-blue/90 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"
|
||||||
|
>
|
||||||
|
<div class="absolute bottom-4 left-4 right-4">
|
||||||
|
<h4 class="game-heading">{{ game.name }}</h4>
|
||||||
|
<button class="button-base w-full py-2">Jetzt Spielen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lg:col-span-1 space-y-6">
|
||||||
|
<div class="card p-4">
|
||||||
|
<h3 class="section-heading text-xl mb-4">Konto</h3>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<button class="bg-deep-blue-light hover:bg-deep-blue-contrast w-full py-2 rounded">
|
||||||
|
Transaktionen
|
||||||
|
</button>
|
||||||
|
<button class="bg-deep-blue-light hover:bg-deep-blue-contrast w-full py-2 rounded">
|
||||||
|
Kontoeinstellungen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card p-4">
|
||||||
|
<h3 class="section-heading text-xl mb-4">Letzte Transaktionen</h3>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div
|
||||||
|
class="flex justify-between items-center"
|
||||||
|
*ngFor="let transaction of recentTransactions"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium">{{ transaction.type }}</p>
|
||||||
|
<p class="text-xs text-text-secondary">{{ transaction.date }}</p>
|
||||||
|
</div>
|
||||||
|
<span [class]="transaction.amount > 0 ? 'text-emerald' : 'text-accent-red'">
|
||||||
|
{{ transaction.amount | currency: 'EUR' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
101
frontend/src/app/feature/home/home.component.ts
Normal file
101
frontend/src/app/feature/home/home.component.ts
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||||
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { DepositComponent } from '../deposit/deposit.component';
|
||||||
|
import { NavbarComponent } from '../../shared/components/navbar/navbar.component';
|
||||||
|
import { CurrencyPipe, NgFor } from '@angular/common';
|
||||||
|
|
||||||
|
interface Game {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
image: string;
|
||||||
|
}
|
||||||
|
interface Transaction {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
amount: number;
|
||||||
|
date: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-homepage',
|
||||||
|
standalone: true,
|
||||||
|
imports: [NavbarComponent, CurrencyPipe, NgFor],
|
||||||
|
templateUrl: './home.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export default class HomeComponent {
|
||||||
|
private keycloakService: KeycloakService = inject(KeycloakService);
|
||||||
|
public dialog: MatDialog = inject(MatDialog);
|
||||||
|
|
||||||
|
userAvatar = '/assets/images/default-avatar.png';
|
||||||
|
username = this.keycloakService.getUsername();
|
||||||
|
vipLevel = 1;
|
||||||
|
balance = 1000.0;
|
||||||
|
|
||||||
|
featuredGames: Game[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'Poker',
|
||||||
|
image: '/poker.webp',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
name: 'Blackjack',
|
||||||
|
image: '/blackjack.webp',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
name: 'Slots',
|
||||||
|
image: '/slots.webp',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
name: 'Plinko',
|
||||||
|
image: '/plinko.webp',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
name: 'Liars Dice',
|
||||||
|
image: '/liars-dice.webp',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
name: 'Lootboxen',
|
||||||
|
image: '/lootbox.webp',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
allGames: Game[] = [...this.featuredGames];
|
||||||
|
|
||||||
|
recentTransactions: Transaction[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: 'Deposit',
|
||||||
|
amount: 100.0,
|
||||||
|
date: '2024-03-20',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: 'Withdrawal',
|
||||||
|
amount: -50.0,
|
||||||
|
date: '2024-03-19',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: 'Bonus',
|
||||||
|
amount: 25.0,
|
||||||
|
date: '2024-03-18',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
public logout() {
|
||||||
|
const baseUrl = window.location.origin;
|
||||||
|
|
||||||
|
this.keycloakService.logout(`${baseUrl}/`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public openDialog() {
|
||||||
|
this.dialog.open(DepositComponent);
|
||||||
|
}
|
||||||
|
}
|
142
frontend/src/app/feature/landing/landing.component.html
Normal file
142
frontend/src/app/feature/landing/landing.component.html
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
<app-navbar></app-navbar>
|
||||||
|
|
||||||
|
<div class="min-h-screen bg-deep-blue text-text-primary">
|
||||||
|
<div class="container mx-auto px-4 py-8 sm:py-12">
|
||||||
|
<div class="max-w-5xl mx-auto">
|
||||||
|
<div class="text-center mb-12 sm:mb-16">
|
||||||
|
<h1 class="text-3xl sm:text-4xl lg:text-5xl section-heading mb-2 sm:mb-3">
|
||||||
|
Willkommensbonus
|
||||||
|
</h1>
|
||||||
|
<div class="welcome-bonus">200% bis zu 500€</div>
|
||||||
|
<p class="bonus-description">+ 200 Freispiele</p>
|
||||||
|
|
||||||
|
<button class="w-full sm:w-auto button-base px-6 sm:px-8 py-3 shadow-lg">
|
||||||
|
Bonus Sichern
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="relative mb-16">
|
||||||
|
<h2 class="text-xl sm:text-2xl section-heading mb-4 sm:mb-6">Beliebte Spiele</h2>
|
||||||
|
<div class="relative group">
|
||||||
|
<div class="overflow-hidden rounded-lg">
|
||||||
|
<div
|
||||||
|
class="slider-container"
|
||||||
|
[style.transform]="'translateX(-' + currentSlide * 100 + '%)'"
|
||||||
|
>
|
||||||
|
<div class="slider-grid">
|
||||||
|
<div class="card">
|
||||||
|
<div class="game-card-content">
|
||||||
|
<h3 class="game-heading-sm">Slots</h3>
|
||||||
|
<p class="game-text">Klassische Spielautomaten</p>
|
||||||
|
<button class="button-base w-full py-2">Jetzt Spielen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="game-card-content">
|
||||||
|
<h3 class="game-heading-sm">Plinko</h3>
|
||||||
|
<p class="game-text">Spannendes Geschicklichkeitsspiel</p>
|
||||||
|
<button class="button-base w-full py-2">Jetzt Spielen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hidden lg:block card">
|
||||||
|
<div class="game-card-content">
|
||||||
|
<h3 class="game-heading-sm">Blackjack</h3>
|
||||||
|
<p class="game-text">Klassisches Kartenspiel</p>
|
||||||
|
<button class="button-base w-full py-2">Jetzt Spielen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="slider-grid">
|
||||||
|
<div class="card">
|
||||||
|
<div class="game-card-content">
|
||||||
|
<h3 class="game-heading-sm">Poker</h3>
|
||||||
|
<p class="game-text">Texas Hold'em & mehr</p>
|
||||||
|
<button class="button-base w-full py-2">Jetzt Spielen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="game-card-content">
|
||||||
|
<h3 class="game-heading-sm">Liars Dice</h3>
|
||||||
|
<p class="game-text">Würfelspiel mit Strategie</p>
|
||||||
|
<button class="button-base w-full py-2">Jetzt Spielen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hidden lg:block card">
|
||||||
|
<div class="game-card-content">
|
||||||
|
<h3 class="game-heading-sm">Lootboxen</h3>
|
||||||
|
<p class="game-text">Überraschungskisten</p>
|
||||||
|
<button class="button-base w-full py-2">Jetzt Spielen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="nav-button left-[-4rem]" (click)="prevSlide()">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M15 19l-7-7 7-7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="nav-button right-[-4rem]" (click)="nextSlide()">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M9 5l7 7-7 7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="absolute -bottom-6 left-1/2 -translate-x-1/2 flex gap-2">
|
||||||
|
<button
|
||||||
|
*ngFor="let _ of [0, 1]; let i = index"
|
||||||
|
[class]="
|
||||||
|
'w-2 h-2 rounded-full transition-all duration-300 ' +
|
||||||
|
(currentSlide === i ? 'bg-emerald w-4' : 'bg-text-tertiary')
|
||||||
|
"
|
||||||
|
(click)="goToSlide(i)"
|
||||||
|
>
|
||||||
|
<span class="sr-only">Slide {{ i + 1 }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 sm:gap-6">
|
||||||
|
<div class="stat-container">
|
||||||
|
<div class="stat-number">50 Mio.€+</div>
|
||||||
|
<div class="stat-text">Ausgezahlt</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-container">
|
||||||
|
<div class="stat-number">10 Mio.+</div>
|
||||||
|
<div class="stat-text">Spiele</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-container">
|
||||||
|
<div class="stat-number">24/7</div>
|
||||||
|
<div class="stat-text">Support <span class="text-emerald text-xs">*</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
55
frontend/src/app/feature/landing/landing.component.ts
Normal file
55
frontend/src/app/feature/landing/landing.component.ts
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import { ChangeDetectionStrategy, Component, OnInit, OnDestroy } from '@angular/core';
|
||||||
|
import { NavbarComponent } from '../../shared/components/navbar/navbar.component';
|
||||||
|
import { NgFor } from '@angular/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-landing-page',
|
||||||
|
standalone: true,
|
||||||
|
imports: [NavbarComponent, NgFor],
|
||||||
|
templateUrl: './landing.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class LandingComponent implements OnInit, OnDestroy {
|
||||||
|
currentSlide = 0;
|
||||||
|
private autoplayInterval: ReturnType<typeof setInterval> | undefined;
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.startAutoplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.stopAutoplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
prevSlide() {
|
||||||
|
this.currentSlide = this.currentSlide === 0 ? 1 : 0;
|
||||||
|
this.resetAutoplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSlide() {
|
||||||
|
this.currentSlide = this.currentSlide === 1 ? 0 : 1;
|
||||||
|
this.resetAutoplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
goToSlide(index: number) {
|
||||||
|
this.currentSlide = index;
|
||||||
|
this.resetAutoplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private startAutoplay() {
|
||||||
|
this.autoplayInterval = setInterval(() => {
|
||||||
|
this.nextSlide();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private stopAutoplay() {
|
||||||
|
if (this.autoplayInterval) {
|
||||||
|
clearInterval(this.autoplayInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private resetAutoplay() {
|
||||||
|
this.stopAutoplay();
|
||||||
|
this.startAutoplay();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<p>Logging in...</p>
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core';
|
||||||
|
import { UserService } from '../../service/user.service';
|
||||||
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login-success',
|
||||||
|
standalone: true,
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './login-success.component.html',
|
||||||
|
styleUrl: './login-success.component.css',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export default class LoginSuccessComponent implements OnInit {
|
||||||
|
private userService: UserService = inject(UserService);
|
||||||
|
private keycloakService: KeycloakService = inject(KeycloakService);
|
||||||
|
private router: Router = inject(Router);
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
const userProfile = await this.keycloakService.loadUserProfile();
|
||||||
|
const user = await this.userService.getOrCreateUser(userProfile);
|
||||||
|
sessionStorage.setItem('user', JSON.stringify(user));
|
||||||
|
|
||||||
|
this.router.navigate(['']);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +0,0 @@
|
||||||
<h3>List of Hellos</h3>
|
|
||||||
<ul>
|
|
||||||
@for(e of employees$ | async; track e.id) {
|
|
||||||
<li>
|
|
||||||
{{e.id }}, {{e.message}}
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { Component } from '@angular/core';
|
|
||||||
import {Observable, of} from "rxjs";
|
|
||||||
import {Hello} from "../Hello";
|
|
||||||
import {HttpClient, HttpClientModule, HttpHeaders, provideHttpClient} from "@angular/common/http";
|
|
||||||
import {AsyncPipe} from "@angular/common";
|
|
||||||
import {KeycloakService} from "keycloak-angular";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-hello-list',
|
|
||||||
standalone: true,
|
|
||||||
imports: [
|
|
||||||
AsyncPipe
|
|
||||||
],
|
|
||||||
providers: [KeycloakService],
|
|
||||||
templateUrl: './hello-list.component.html',
|
|
||||||
styleUrl: './hello-list.component.css'
|
|
||||||
})
|
|
||||||
export class HelloListComponent {
|
|
||||||
employees$: Observable<Hello[]>;
|
|
||||||
|
|
||||||
constructor(private http: HttpClient) {
|
|
||||||
this.employees$ = of([]);
|
|
||||||
this.fetchData();
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchData() {
|
|
||||||
this.employees$ = this.http.get<Hello[]>('/backend/hellos');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
5
frontend/src/app/model/User.ts
Normal file
5
frontend/src/app/model/User.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export interface User {
|
||||||
|
keycloakId: string;
|
||||||
|
username: string;
|
||||||
|
balance: number;
|
||||||
|
}
|
14
frontend/src/app/service/deposit.service.ts
Normal file
14
frontend/src/app/service/deposit.service.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { inject, Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class DepositService {
|
||||||
|
private http: HttpClient = inject(HttpClient);
|
||||||
|
|
||||||
|
handleDeposit(amount: number): Observable<{ sessionId: string }> {
|
||||||
|
return this.http.post<{ sessionId: string }>('/backend/deposit/checkout', { amount });
|
||||||
|
}
|
||||||
|
}
|
38
frontend/src/app/service/user.service.ts
Normal file
38
frontend/src/app/service/user.service.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { inject, Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { KeycloakProfile } from 'keycloak-js';
|
||||||
|
import { catchError, EMPTY, Observable } from 'rxjs';
|
||||||
|
import { User } from '../model/User';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class UserService {
|
||||||
|
private http: HttpClient = inject(HttpClient);
|
||||||
|
|
||||||
|
public getUser(id: string): Observable<User | null> {
|
||||||
|
return this.http.get<User | null>(`/backend/user/${id}`).pipe(catchError(() => EMPTY));
|
||||||
|
}
|
||||||
|
|
||||||
|
public createUser(id: string, username: string): Observable<User> {
|
||||||
|
return this.http.post<User>('/backend/user', {
|
||||||
|
keycloakId: id,
|
||||||
|
username: username,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getOrCreateUser(userProfile: KeycloakProfile) {
|
||||||
|
if (userProfile.id == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return await this.getUser(userProfile.id)
|
||||||
|
.toPromise()
|
||||||
|
.then(async (user) => {
|
||||||
|
if (user) {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.createUser(userProfile.id ?? '', userProfile.username ?? '').toPromise();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
<footer class="bg-deep-blue mt-auto">
|
||||||
|
<div class="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-8">
|
||||||
|
<div class="footer-section">
|
||||||
|
<h3 class="footer-heading">Casino Spiele</h3>
|
||||||
|
<ul class="space-y-3">
|
||||||
|
<li>
|
||||||
|
<a routerLink="/games" class="footer-link">Slots</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a routerLink="/games" class="footer-link">Plinko</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a routerLink="/games" class="footer-link">Blackjack</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a routerLink="/games" class="footer-link">Poker</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a routerLink="/games" class="footer-link">Liars Dice</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="footer-section">
|
||||||
|
<h3 class="footer-heading">Andere Spiele</h3>
|
||||||
|
<ul class="space-y-3">
|
||||||
|
<li>
|
||||||
|
<a routerLink="/games" class="footer-link">Lootboxen</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-2 lg:col-span-2">
|
||||||
|
<h3 class="footer-heading">Einzahlungsmöglichkeiten</h3>
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||||
|
<div class="footer-payment-method">
|
||||||
|
<fa-icon [icon]="faMoneyBillTransfer" class="footer-payment-icon"></fa-icon>
|
||||||
|
<span class="footer-payment-text">Sofort</span>
|
||||||
|
</div>
|
||||||
|
<div class="footer-payment-method">
|
||||||
|
<fa-icon [icon]="faPaypal" class="footer-payment-icon"></fa-icon>
|
||||||
|
<span class="footer-payment-text">Paypal</span>
|
||||||
|
</div>
|
||||||
|
<div class="footer-payment-method">
|
||||||
|
<fa-icon [icon]="faCreditCard" class="footer-payment-icon"></fa-icon>
|
||||||
|
<span class="footer-payment-text">Kreditkarte</span>
|
||||||
|
</div>
|
||||||
|
<div class="footer-payment-method">
|
||||||
|
<fa-icon [icon]="faWallet" class="footer-payment-icon"></fa-icon>
|
||||||
|
<span class="footer-payment-text">Klara</span>
|
||||||
|
</div>
|
||||||
|
<div class="footer-payment-method">
|
||||||
|
<fa-icon [icon]="faMoneyBillTransfer" class="footer-payment-icon"></fa-icon>
|
||||||
|
<span class="footer-payment-text">Sepa</span>
|
||||||
|
</div>
|
||||||
|
<div class="footer-payment-method">
|
||||||
|
<fa-icon [icon]="faGooglePay" class="footer-payment-icon"></fa-icon>
|
||||||
|
<span class="footer-payment-text">Google Pay</span>
|
||||||
|
</div>
|
||||||
|
<div class="footer-payment-method">
|
||||||
|
<fa-icon [icon]="faApplePay" class="footer-payment-icon"></fa-icon>
|
||||||
|
<span class="footer-payment-text">Apple Pay</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-12 pt-8 border-t border-deep-blue-light">
|
||||||
|
<div class="flex flex-col md:flex-row justify-between items-center">
|
||||||
|
<div class="footer-copyright">
|
||||||
|
<span class="footer-disclaimer">
|
||||||
|
<span class="text-emerald">*</span> nicht vorhanden.
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
© {{ currentYear }} Trustworthy Casino. Keine Rechte vorbehalten.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
|
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
||||||
|
import { faMoneyBillTransfer, faCreditCard, faWallet } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { faPaypal, faGooglePay, faApplePay } from '@fortawesome/free-brands-svg-icons';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-footer',
|
||||||
|
standalone: true,
|
||||||
|
templateUrl: './footer.component.html',
|
||||||
|
imports: [FontAwesomeModule],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class FooterComponent {
|
||||||
|
currentYear: number = new Date().getFullYear();
|
||||||
|
|
||||||
|
faPaypal = faPaypal;
|
||||||
|
faCreditCard = faCreditCard;
|
||||||
|
faMoneyBillTransfer = faMoneyBillTransfer;
|
||||||
|
faWallet = faWallet;
|
||||||
|
faGooglePay = faGooglePay;
|
||||||
|
faApplePay = faApplePay;
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
<nav class="bg-deep-blue border-b border-deep-blue-contrast">
|
||||||
|
<div class="max-w-full mx-auto px-4">
|
||||||
|
<div class="flex justify-between items-center h-14">
|
||||||
|
<div class="flex items-center space-x-6">
|
||||||
|
<a routerLink="/" class="nav-brand">
|
||||||
|
<span>Trustworthy Casino</span>
|
||||||
|
</a>
|
||||||
|
<div class="hidden md:flex items-center space-x-1">
|
||||||
|
<a routerLink="/games" class="nav-link">Spiele</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hidden md:flex items-center space-x-4">
|
||||||
|
@if (!isLoggedIn) {
|
||||||
|
<button (click)="login()" class="button-base px-4 py-1.5">Anmelden</button>
|
||||||
|
}
|
||||||
|
@if (isLoggedIn) {
|
||||||
|
<button (click)="logout()" class="button-base px-4 py-1.5">Abmelden</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="md:hidden">
|
||||||
|
<button (click)="toggleMenu()" class="nav-toggle">
|
||||||
|
<svg
|
||||||
|
class="h-6 w-6"
|
||||||
|
[class.hidden]="isMenuOpen"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M4 6h16M4 12h16M4 18h16"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
class="h-6 w-6"
|
||||||
|
[class.hidden]="!isMenuOpen"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div [class]="isMenuOpen ? 'block' : 'hidden'" class="md:hidden">
|
||||||
|
<div class="nav-mobile-menu">
|
||||||
|
<a routerLink="/games" class="nav-mobile-link">Spiele</a>
|
||||||
|
<div class="pt-2 space-y-2">
|
||||||
|
@if (!isLoggedIn) {
|
||||||
|
<button (click)="login()" class="button-base w-full py-1.5">Anmelden</button>
|
||||||
|
}
|
||||||
|
@if (isLoggedIn) {
|
||||||
|
<button (click)="logout()" class="button-base w-full py-1.5">Abmelden</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-navbar',
|
||||||
|
templateUrl: './navbar.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [RouterModule],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class NavbarComponent {
|
||||||
|
isMenuOpen = false;
|
||||||
|
private keycloakService: KeycloakService = inject(KeycloakService);
|
||||||
|
|
||||||
|
isLoggedIn = this.keycloakService.isLoggedIn();
|
||||||
|
|
||||||
|
login() {
|
||||||
|
try {
|
||||||
|
const baseUrl = window.location.origin;
|
||||||
|
this.keycloakService.login({ redirectUri: `${baseUrl}/login/success` });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Login failed:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
this.keycloakService.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMenu() {
|
||||||
|
this.isMenuOpen = !this.isMenuOpen;
|
||||||
|
}
|
||||||
|
}
|
4
frontend/src/environments/environment.ts
Normal file
4
frontend/src/environments/environment.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export const environment = {
|
||||||
|
STRIPE_KEY:
|
||||||
|
'pk_test_51QrePYIvCfqz7ANgMizBorPpVjJ8S6gcaL4yvcMQnVaKyReqcQ6jqaQEF7aDZbDu8rNVsTZrw8ABek4ToxQX7KZe00jpGh8naG',
|
||||||
|
};
|
|
@ -1,13 +1,13 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<title>Lf10StarterNew</title>
|
<title>Lf10StarterNew</title>
|
||||||
<base href="/">
|
<base href="/" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app-root></app-root>
|
<app-root></app-root>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -2,10 +2,4 @@ import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
import { appConfig } from './app/app.config';
|
import { appConfig } from './app/app.config';
|
||||||
import { AppComponent } from './app/app.component';
|
import { AppComponent } from './app/app.component';
|
||||||
|
|
||||||
|
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));
|
||||||
|
|
||||||
bootstrapApplication(AppComponent, appConfig)
|
|
||||||
.catch((err) => console.error(err));
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1 +1,147 @@
|
||||||
/* You can add global styles to this file, and also import other style files */
|
@import 'tailwindcss';
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--color-deep-blue: #0a1219;
|
||||||
|
--color-deep-blue-light: #121e27;
|
||||||
|
--color-deep-blue-contrast: #1a2835;
|
||||||
|
|
||||||
|
--color-emerald: #10b981;
|
||||||
|
--color-emerald-dark: #059669;
|
||||||
|
--color-emerald-light: #34d399;
|
||||||
|
|
||||||
|
--color-text-primary: #ffffff;
|
||||||
|
--color-text-secondary: #94a3b8;
|
||||||
|
--color-text-tertiary: #64748b;
|
||||||
|
|
||||||
|
--color-accent-yellow: #fbbf24;
|
||||||
|
--color-accent-red: #ef4444;
|
||||||
|
--color-accent-purple: #8b5cf6;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
@apply bg-deep-blue text-text-primary h-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
a {
|
||||||
|
@apply cursor-pointer active:scale-95 text-text-primary transition-all duration-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
@apply bg-deep-blue-contrast rounded-lg overflow-hidden shadow-lg hover:shadow-xl transition-shadow duration-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-base {
|
||||||
|
@apply bg-emerald hover:bg-emerald-dark text-text-primary transition-all duration-300 active:scale-95 rounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card-content {
|
||||||
|
@apply p-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button {
|
||||||
|
@apply hidden lg:block absolute top-1/2 -translate-y-1/2 bg-deep-blue-contrast hover:bg-deep-blue-contrast/90 text-text-primary p-3 rounded-full opacity-0 group-hover:opacity-100 transition-all duration-300 shadow-lg hover:scale-110;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-container {
|
||||||
|
@apply flex transition-transform duration-500 ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-grid {
|
||||||
|
@apply min-w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-bonus {
|
||||||
|
@apply text-4xl sm:text-5xl lg:text-7xl font-extrabold text-emerald-light mb-3 sm:mb-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bonus-description {
|
||||||
|
@apply text-text-secondary text-base sm:text-lg mb-6 sm:mb-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-heading {
|
||||||
|
@apply font-bold text-text-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-heading-sm {
|
||||||
|
@apply font-bold text-text-primary text-sm mb-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-heading-xl {
|
||||||
|
@apply font-bold text-text-primary text-xl mb-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-text {
|
||||||
|
@apply text-text-secondary text-sm mb-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-container {
|
||||||
|
@apply bg-deep-blue-contrast rounded-lg shadow-lg p-4 sm:p-6 text-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number {
|
||||||
|
@apply text-xl sm:text-2xl font-bold text-emerald;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-text {
|
||||||
|
@apply text-text-secondary text-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-brand {
|
||||||
|
@apply flex items-center text-text-primary text-xl font-semibold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
@apply px-3 py-2 rounded-md font-normal text-sm text-text-secondary hover:text-text-primary hover:bg-deep-blue-contrast transition-all duration-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-toggle {
|
||||||
|
@apply text-text-secondary hover:text-text-primary transition-colors duration-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-mobile-menu {
|
||||||
|
@apply p-2 pt-2 mb-4 space-y-1 bg-deep-blue-contrast rounded-b-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-mobile-link {
|
||||||
|
@apply block px-3 py-2 rounded-md text-sm text-text-secondary hover:text-text-primary hover:bg-deep-blue-light transition-all duration-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-section {
|
||||||
|
@apply col-span-2 md:col-span-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-heading {
|
||||||
|
@apply text-text-primary text-sm font-semibold mb-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-link {
|
||||||
|
@apply text-text-secondary hover:text-text-primary text-sm transition-all duration-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-payment-method {
|
||||||
|
@apply bg-deep-blue rounded p-3 flex items-center justify-center space-x-2 hover:bg-deep-blue/50 transition-all duration-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-payment-icon {
|
||||||
|
@apply text-text-secondary text-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-payment-text {
|
||||||
|
@apply text-text-secondary text-xs whitespace-nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-copyright {
|
||||||
|
@apply text-text-secondary text-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-disclaimer {
|
||||||
|
@apply text-xs;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-dialog-container {
|
||||||
|
--mdc-dialog-container-color: var(--color-deep-blue-light) important;
|
||||||
|
--mdc-dialog-subhead-color: var(--color-text-primary) important;
|
||||||
|
--mdc-dialog-supporting-text-color: var(--color-text-secondary) important;
|
||||||
|
--mdc-dialog-container-shape: 6px important;
|
||||||
|
}
|
||||||
|
|
64
release.config.cjs
Normal file
64
release.config.cjs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
module.exports = {
|
||||||
|
branches: ["main"],
|
||||||
|
plugins: [
|
||||||
|
[
|
||||||
|
"@semantic-release/commit-analyzer",
|
||||||
|
{
|
||||||
|
preset: "angular",
|
||||||
|
releaseRules: [
|
||||||
|
{ type: "feat", release: "minor" },
|
||||||
|
{ type: "fix", release: "patch" },
|
||||||
|
{ type: "perf", release: "patch" },
|
||||||
|
{ type: "docs", release: "patch" },
|
||||||
|
{ type: "style", release: "patch" },
|
||||||
|
{ type: "refactor", release: "patch" },
|
||||||
|
{ type: "test", release: "patch" },
|
||||||
|
{ type: "chore", release: "patch" },
|
||||||
|
{ type: "ci", release: "patch" },
|
||||||
|
{ type: "build", release: "patch" },
|
||||||
|
{ type: "revert", release: "patch" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"@semantic-release/release-notes-generator",
|
||||||
|
{
|
||||||
|
preset: "conventionalcommits",
|
||||||
|
presetConfig: {
|
||||||
|
types: [
|
||||||
|
{ type: "feat", section: "Features", hidden: false },
|
||||||
|
{ type: "fix", section: "Bug Fixes", hidden: false },
|
||||||
|
{
|
||||||
|
type: "perf",
|
||||||
|
section: "Performance Improvements",
|
||||||
|
hidden: false,
|
||||||
|
},
|
||||||
|
{ type: "docs", section: "Documentation", hidden: false },
|
||||||
|
{ type: "style", section: "Code Style", hidden: false },
|
||||||
|
{ type: "refactor", section: "Code Refactoring", hidden: false },
|
||||||
|
{ type: "test", section: "Tests", hidden: false },
|
||||||
|
{ type: "chore", section: "Chores", hidden: false },
|
||||||
|
{ type: "ci", section: "Continuous Integration", hidden: false },
|
||||||
|
{ type: "build", section: "Build System", hidden: false },
|
||||||
|
{ type: "revert", section: "Reverts", hidden: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
parserOpts: {
|
||||||
|
noteKeywords: ["BREAKING CHANGE", "BREAKING CHANGES", "BREAKING"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"@semantic-release/changelog",
|
||||||
|
{
|
||||||
|
changelogFile: "CHANGELOG.md",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"@saithodev/semantic-release-gitea",
|
||||||
|
{
|
||||||
|
giteaUrl: "https://git.simonis.lol",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
};
|
Reference in a new issue