diff --git a/.metadata/.lock_info b/.metadata/.lock_info
index a3ed7c9..37158f9 100644
--- a/.metadata/.lock_info
+++ b/.metadata/.lock_info
@@ -1,5 +1,5 @@
-#Mon Mar 23 15:59:12 CET 2026
+#Mon Mar 23 21:09:40 CET 2026
display=\:0
-host=Mario-Linux
-process-id=144468
+host=mario-mint
+process-id=80279
user=mario
diff --git a/.metadata/.log b/.metadata/.log
index 7437f2f..92e2ba3 100644
--- a/.metadata/.log
+++ b/.metadata/.log
@@ -44,3 +44,185 @@ Command-line arguments: -os linux -ws gtk -arch x86_64 -clean -product org.ecli
!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-23 15:59:13.162
!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
+!SESSION 2026-03-23 17:25:44.070 -----------------------------------------------
+eclipse.buildId=4.39.0.20260305-0817
+java.version=21.0.6
+java.vendor=Eclipse Adoptium
+BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=de_DE
+Framework arguments: -product org.eclipse.epp.package.java.product
+Command-line arguments: -os linux -ws gtk -arch x86_64 -clean -product org.eclipse.epp.package.java.product
+
+!ENTRY ch.qos.logback.classic 1 0 2026-03-23 17:25:45.609
+!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized.
+
+!ENTRY ch.qos.logback.classic 1 0 2026-03-23 17:25:48.467
+!MESSAGE Logback config file: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.m2e.logback/logback.2.7.101.20251017-1242.xml
+
+!ENTRY org.eclipse.ui 2 0 2026-03-23 17:25:48.662
+!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
+!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-23 17:25:48.662
+!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
+
+!ENTRY org.eclipse.ui 2 0 2026-03-23 17:25:48.852
+!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
+!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-23 17:25:48.852
+!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
+
+!ENTRY org.springframework.tooling.boot.ls 1 0 2026-03-23 17:26:29.969
+!MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS
+!SESSION 2026-03-23 17:26:31.429 -----------------------------------------------
+eclipse.buildId=4.39.0.20260305-0817
+java.version=21.0.6
+java.vendor=Eclipse Adoptium
+BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=de_DE
+Framework arguments: -product org.eclipse.epp.package.java.product
+Command-line arguments: -os linux -ws gtk -arch x86_64 -clean -product org.eclipse.epp.package.java.product
+
+!ENTRY ch.qos.logback.classic 1 0 2026-03-23 17:26:32.616
+!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized.
+
+!ENTRY ch.qos.logback.classic 1 0 2026-03-23 17:26:35.371
+!MESSAGE Logback config file: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.m2e.logback/logback.2.7.101.20251017-1242.xml
+
+!ENTRY org.eclipse.ui 2 0 2026-03-23 17:26:35.486
+!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
+!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-23 17:26:35.486
+!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
+
+!ENTRY org.eclipse.ui 2 0 2026-03-23 17:26:35.621
+!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
+!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-23 17:26:35.621
+!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
+
+!ENTRY org.springframework.tooling.boot.ls 1 0 2026-03-23 17:29:17.229
+!MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS
+!SESSION 2026-03-23 17:36:46.699 -----------------------------------------------
+eclipse.buildId=4.39.0.20260305-0817
+java.version=21.0.6
+java.vendor=Eclipse Adoptium
+BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=de_DE
+Framework arguments: -product org.eclipse.epp.package.java.product
+Command-line arguments: -os linux -ws gtk -arch x86_64 -clean -product org.eclipse.epp.package.java.product
+
+!ENTRY ch.qos.logback.classic 1 0 2026-03-23 17:36:48.204
+!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized.
+
+!ENTRY ch.qos.logback.classic 1 0 2026-03-23 17:36:52.468
+!MESSAGE Logback config file: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.m2e.logback/logback.2.7.101.20251017-1242.xml
+
+!ENTRY org.eclipse.ui 2 0 2026-03-23 17:36:52.616
+!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
+!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-23 17:36:52.616
+!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
+
+!ENTRY org.eclipse.ui 2 0 2026-03-23 17:36:52.744
+!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
+!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-23 17:36:52.744
+!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
+
+!ENTRY org.springframework.tooling.boot.ls 1 0 2026-03-23 17:38:14.624
+!MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS
+!SESSION 2026-03-23 17:38:45.521 -----------------------------------------------
+eclipse.buildId=4.39.0.20260305-0817
+java.version=21.0.6
+java.vendor=Eclipse Adoptium
+BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=de_DE
+Framework arguments: -product org.eclipse.epp.package.java.product
+Command-line arguments: -os linux -ws gtk -arch x86_64 -clean -product org.eclipse.epp.package.java.product
+
+!ENTRY ch.qos.logback.classic 1 0 2026-03-23 17:38:46.707
+!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized.
+
+!ENTRY ch.qos.logback.classic 1 0 2026-03-23 17:38:49.211
+!MESSAGE Logback config file: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.m2e.logback/logback.2.7.101.20251017-1242.xml
+
+!ENTRY org.eclipse.ui 2 0 2026-03-23 17:38:49.361
+!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
+!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-23 17:38:49.361
+!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
+
+!ENTRY org.eclipse.ui 2 0 2026-03-23 17:38:49.491
+!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
+!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-23 17:38:49.492
+!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
+
+!ENTRY org.eclipse.jface 2 0 2026-03-23 17:49:15.882
+!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation.
+!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-23 17:49:15.882
+!MESSAGE A conflict occurred for CTRL+SHIFT+T:
+Binding(CTRL+SHIFT+T,
+ ParameterizedCommand(Command(org.eclipse.jdt.ui.navigate.open.type,Open Type,
+ Open a type in a Java editor,
+ Category(org.eclipse.ui.category.navigate,Navigate,null,true),
+ WorkbenchHandlerServiceHandler("org.eclipse.jdt.ui.navigate.open.type"),
+ ,,true),null),
+ org.eclipse.ui.defaultAcceleratorConfiguration,
+ org.eclipse.ui.contexts.window,,,system)
+Binding(CTRL+SHIFT+T,
+ ParameterizedCommand(Command(org.eclipse.lsp4e.symbolInWorkspace,Go to Symbol in Workspace,
+ ,
+ Category(org.eclipse.lsp4e.category,Language Servers,null,true),
+ WorkbenchHandlerServiceHandler("org.eclipse.lsp4e.symbolInWorkspace"),
+ ,,true),null),
+ org.eclipse.ui.defaultAcceleratorConfiguration,
+ org.eclipse.ui.contexts.window,,,system)
+
+!ENTRY org.eclipse.debug.core 4 125 2026-03-23 17:50:18.648
+!MESSAGE Error logged from Debug Core:
+!STACK 0
+java.io.IOException: Stream closed
+ at java.base/java.io.BufferedInputStream.getBufIfOpen(BufferedInputStream.java:188)
+ at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:343)
+ at java.base/java.io.BufferedInputStream.implRead(BufferedInputStream.java:420)
+ at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:405)
+ at java.base/java.io.FilterInputStream.read(FilterInputStream.java:95)
+ at org.eclipse.debug.internal.core.OutputStreamMonitor.internalRead(OutputStreamMonitor.java:235)
+ at org.eclipse.debug.internal.core.OutputStreamMonitor.read(OutputStreamMonitor.java:211)
+ at java.base/java.lang.Thread.run(Thread.java:1583)
+
+!ENTRY org.springframework.tooling.boot.ls 1 0 2026-03-23 19:07:16.440
+!MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS
+!SESSION 2026-03-23 21:09:34.939 -----------------------------------------------
+eclipse.buildId=4.39.0.20260305-0817
+java.version=21.0.6
+java.vendor=Eclipse Adoptium
+BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=de_DE
+Framework arguments: -product org.eclipse.epp.package.java.product
+Command-line arguments: -os linux -ws gtk -arch x86_64 -clean -product org.eclipse.epp.package.java.product
+
+!ENTRY ch.qos.logback.classic 1 0 2026-03-23 21:09:36.148
+!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized.
+
+!ENTRY ch.qos.logback.classic 1 0 2026-03-23 21:09:41.162
+!MESSAGE Logback config file: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.m2e.logback/logback.2.7.101.20251017-1242.xml
+
+!ENTRY org.eclipse.ui 2 0 2026-03-23 21:09:41.310
+!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
+!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-23 21:09:41.310
+!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
+
+!ENTRY org.eclipse.ui 2 0 2026-03-23 21:09:41.447
+!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
+!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-23 21:09:41.447
+!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
+
+!ENTRY org.eclipse.jface 2 0 2026-03-23 21:57:04.900
+!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation.
+!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-23 21:57:04.900
+!MESSAGE A conflict occurred for CTRL+SHIFT+T:
+Binding(CTRL+SHIFT+T,
+ ParameterizedCommand(Command(org.eclipse.jdt.ui.navigate.open.type,Open Type,
+ Open a type in a Java editor,
+ Category(org.eclipse.ui.category.navigate,Navigate,null,true),
+ WorkbenchHandlerServiceHandler("org.eclipse.jdt.ui.navigate.open.type"),
+ ,,true),null),
+ org.eclipse.ui.defaultAcceleratorConfiguration,
+ org.eclipse.ui.contexts.window,,,system)
+Binding(CTRL+SHIFT+T,
+ ParameterizedCommand(Command(org.eclipse.lsp4e.symbolInWorkspace,Go to Symbol in Workspace,
+ ,
+ Category(org.eclipse.lsp4e.category,Language Servers,null,true),
+ WorkbenchHandlerServiceHandler("org.eclipse.lsp4e.symbolInWorkspace"),
+ ,,true),null),
+ org.eclipse.ui.defaultAcceleratorConfiguration,
+ org.eclipse.ui.contexts.window,,,system)
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.index b/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.index
index 32e84ce..12acdc0 100644
Binary files a/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.index and b/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.index differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources b/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources
index e9801a8..adb53a5 100644
Binary files a/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources and b/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources differ
diff --git a/.metadata/.plugins/org.eclipse.e4.workbench/workbench.xmi b/.metadata/.plugins/org.eclipse.e4.workbench/workbench.xmi
index 3938c96..6bc4c75 100644
--- a/.metadata/.plugins/org.eclipse.e4.workbench/workbench.xmi
+++ b/.metadata/.plugins/org.eclipse.e4.workbench/workbench.xmi
@@ -1,8 +1,8 @@
-
-
+
+
activeSchemeId:org.eclipse.ui.defaultAcceleratorConfiguration
-
+
@@ -10,10 +10,9 @@
topLevel
- shellMaximized
-
-
-
+
+
+
persp.actionSet:org.eclipse.mylyn.tasks.ui.navigation
persp.actionSet:org.eclipse.ui.cheatsheets.actionSet
@@ -83,2583 +82,2667 @@
persp.editorOnboardingCommand:Show Key Assist$$$Shift+Ctrl+L
persp.editorOnboardingCommand:New$$$Ctrl+N
persp.editorOnboardingCommand:Open Type$$$Shift+Ctrl+T
-
-
-
-
+
+
+
+
org.eclipse.e4.primaryNavigationStack
active
noFocus
-
+
View
categoryTag:Java
-
+
View
categoryTag:Java
-
+
View
categoryTag:General
-
+
View
categoryTag:Java
-
-
+
+
View
categoryTag:Spring
-
-
+
+
View
categoryTag:Git
-
-
-
-
+
+
+
+
org.eclipse.e4.secondaryNavigationStack
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
View
categoryTag:Mylyn
-
+
View
categoryTag:Java
-
+
View
categoryTag:Ant
-
+
org.eclipse.e4.secondaryDataStack
-
+ Oomph
+ Gradle
+
View
categoryTag:General
-
+
View
categoryTag:Java
-
+
View
categoryTag:Java
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
View
categoryTag:Terminal
+
+ View
+ categoryTag:Gradle
+
+
+ View
+ categoryTag:Gradle
+
+
+ View
+ categoryTag:Oomph
+ NoRestore
+
-
-
+
+
View
categoryTag:Help
-
+
View
categoryTag:General
-
+
View
categoryTag:Help
-
+
View
categoryTag:Help
-
+
View
categoryTag:General
-
+
ViewMenu
menuContribution:menu
-
+
-
+
View
categoryTag:Help
-
-
+
+
EditorStack
org.eclipse.e4.primaryDataStack
+
+
+ Editor
+ removeOnHide
+ org.eclipse.jdt.ui.CompilationUnitEditor
+
+
+
+ Editor
+ removeOnHide
+ org.eclipse.jdt.ui.CompilationUnitEditor
+
+
+
+ Editor
+ removeOnHide
+ SpringBootPropertyEditor
+
-
+
-
+
View
categoryTag:Java
active
- activeOnClose
-
+
ViewMenu
menuContribution:menu
-
+
-
+
View
categoryTag:Java
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
ViewMenu
menuContribution:menu
-
+
-
+
View
categoryTag:Java
-
+
View
categoryTag:Java
-
+
View
categoryTag:General
-
+
+
View
categoryTag:General
+
+ ViewMenu
+ menuContribution:menu
+
+
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
ViewMenu
menuContribution:menu
-
+
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
View
categoryTag:Mylyn
-
+
View
categoryTag:Terminal
-
+
View
categoryTag:Java
-
+
View
categoryTag:Git
-
+
View
categoryTag:Java
-
+
View
categoryTag:Spring
-
+
ViewMenu
menuContribution:menu
-
+
-
+
View
categoryTag:Ant
-
-
+
+
+
+
+ View
+ categoryTag:Gradle
+
+ ViewMenu
+ menuContribution:menu
+
+
+
+
+
+
+
+ View
+ categoryTag:Gradle
+
+ ViewMenu
+ menuContribution:menu
+
+
+
+
+
+
+
+ View
+ categoryTag:Oomph
+ NoRestore
+
+ ViewMenu
+ menuContribution:menu
+
+
+
+
+
toolbarSeparator
-
+
-
+
Draggable
-
+
-
+
toolbarSeparator
-
+
-
+
Draggable
-
-
+
+
-
+
toolbarSeparator
-
+
-
+
Draggable
-
+
Draggable
-
+
Draggable
-
+
+ Draggable
+
+
toolbarSeparator
-
+
-
+
Draggable
-
+
-
- toolbarSeparator
-
-
-
- toolbarSeparator
-
-
-
+
Draggable
-
+
+ Draggable
+
+
+ toolbarSeparator
+
+
+
+ toolbarSeparator
+
+
+
+ Draggable
+
+
stretch
SHOW_RESTORE_MENU
-
+
Draggable
HIDEABLE
SHOW_RESTORE_MENU
-
-
+
+
stretch
-
+
Draggable
-
+
Draggable
-
-
+
+
TrimStack
Draggable
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
platform:gtk
-
-
-
-
+
+
+
+
platform:gtk
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
platform:gtk
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Editor
removeOnHide
-
+
View
categoryTag:Ant
-
+
View
categoryTag:Gradle
-
+
View
categoryTag:Gradle
-
+
View
categoryTag:Debug
-
+
View
categoryTag:Debug
-
+
View
categoryTag:Debug
-
+
View
categoryTag:Debug
-
+
View
categoryTag:Debug
-
+
View
categoryTag:Debug
-
+
View
categoryTag:Debug
-
+
View
categoryTag:Debug
-
+
View
categoryTag:Java
-
+
View
categoryTag:Git
-
+
View
categoryTag:Git
-
+
View
categoryTag:Git
-
+
View
categoryTag:Git
NoRestore
-
+
View
categoryTag:Git
-
+
View
categoryTag:Help
-
+
View
categoryTag:Java
-
+
View
categoryTag:Java
-
+
View
categoryTag:Debug
-
+
View
categoryTag:Java
-
+
View
categoryTag:Java
-
+
View
categoryTag:Java
-
+
View
categoryTag:Java Browsing
-
+
View
categoryTag:Java Browsing
-
+
View
categoryTag:Java Browsing
-
+
View
categoryTag:Java Browsing
-
+
View
categoryTag:Java
-
+
View
categoryTag:General
-
+
View
categoryTag:Java
-
+
View
categoryTag:Java
-
+
View
categoryTag:Docker
-
+
View
categoryTag:Docker
-
+
View
categoryTag:Docker
-
+
View
categoryTag:Docker
-
+
View
categoryTag:Language Servers
-
+
View
categoryTag:Language Servers
-
+
View
categoryTag:Language Servers
-
+
View
categoryTag:Maven
-
+
View
categoryTag:Maven
-
+
View
categoryTag:Maven
-
+
View
categoryTag:Mylyn
-
+
View
categoryTag:Mylyn
-
+
View
categoryTag:Mylyn
-
+
View
categoryTag:Mylyn
-
+
View
categoryTag:Mylyn
-
+
View
categoryTag:Mylyn
-
+
View
categoryTag:Oomph
-
+
View
categoryTag:Oomph
NoRestore
-
+
View
categoryTag:Plug-in Development
-
+
View
categoryTag:General
-
+
View
categoryTag:Version Control (Team)
-
+
View
categoryTag:Version Control (Team)
-
+
View
categoryTag:Terminal
-
+
View
categoryTag:Help
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
View
categoryTag:Help
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
View
categoryTag:General
-
+
View
categoryTag:Spring
-
+
View
categoryTag:Spring
-
+
View
categoryTag:Spring
-
-
+
+
glue
move_after:PerspectiveSpacer
SHOW_RESTORE_MENU
-
+
move_after:Spacer Glue
HIDEABLE
SHOW_RESTORE_MENU
-
+
glue
move_after:SearchField
SHOW_RESTORE_MENU
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.metadata/.plugins/org.eclipse.jdt.core/externalFilesCache b/.metadata/.plugins/org.eclipse.jdt.core/externalFilesCache
index 593f470..0f60b77 100644
Binary files a/.metadata/.plugins/org.eclipse.jdt.core/externalFilesCache and b/.metadata/.plugins/org.eclipse.jdt.core/externalFilesCache differ
diff --git a/.metadata/.plugins/org.eclipse.jdt.core/nonChainingJarsCache b/.metadata/.plugins/org.eclipse.jdt.core/nonChainingJarsCache
index 593f470..31c1d00 100644
Binary files a/.metadata/.plugins/org.eclipse.jdt.core/nonChainingJarsCache and b/.metadata/.plugins/org.eclipse.jdt.core/nonChainingJarsCache differ
diff --git a/.metadata/.plugins/org.eclipse.jdt.core/variablesAndContainers.dat b/.metadata/.plugins/org.eclipse.jdt.core/variablesAndContainers.dat
index 0edae4b..c95e62e 100644
Binary files a/.metadata/.plugins/org.eclipse.jdt.core/variablesAndContainers.dat and b/.metadata/.plugins/org.eclipse.jdt.core/variablesAndContainers.dat differ
diff --git a/.metadata/.plugins/org.eclipse.jdt.launching/.install.xml b/.metadata/.plugins/org.eclipse.jdt.launching/.install.xml
index a704bef..e0b7cee 100644
--- a/.metadata/.plugins/org.eclipse.jdt.launching/.install.xml
+++ b/.metadata/.plugins/org.eclipse.jdt.launching/.install.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/.metadata/.plugins/org.eclipse.jdt.ui/OpenTypeHistory.xml b/.metadata/.plugins/org.eclipse.jdt.ui/OpenTypeHistory.xml
index a4ee3cb..3a0d75d 100644
--- a/.metadata/.plugins/org.eclipse.jdt.ui/OpenTypeHistory.xml
+++ b/.metadata/.plugins/org.eclipse.jdt.ui/OpenTypeHistory.xml
@@ -1,2 +1,4 @@
-
+
+
+
diff --git a/.metadata/.plugins/org.eclipse.jdt.ui/dialog_settings.xml b/.metadata/.plugins/org.eclipse.jdt.ui/dialog_settings.xml
index a9f9fe1..3ca8ba8 100644
--- a/.metadata/.plugins/org.eclipse.jdt.ui/dialog_settings.xml
+++ b/.metadata/.plugins/org.eclipse.jdt.ui/dialog_settings.xml
@@ -2,9 +2,27 @@
diff --git a/.metadata/.plugins/org.eclipse.m2e.logback/0.log b/.metadata/.plugins/org.eclipse.m2e.logback/0.log
index 9937ed8..7fc35ce 100644
--- a/.metadata/.plugins/org.eclipse.m2e.logback/0.log
+++ b/.metadata/.plugins/org.eclipse.m2e.logback/0.log
@@ -1,2 +1,7 @@
2026-03-23 15:58:46,968 [Worker-6: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is not available. Remote download required.
2026-03-23 15:59:14,530 [Worker-7: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
+2026-03-23 17:25:50,444 [Worker-6: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
+2026-03-23 17:26:37,131 [Worker-5: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
+2026-03-23 17:36:54,482 [Worker-8: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
+2026-03-23 17:38:51,039 [Worker-7: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
+2026-03-23 21:09:44,347 [Worker-7: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
diff --git a/.metadata/.plugins/org.eclipse.ui.workbench/dialog_settings.xml b/.metadata/.plugins/org.eclipse.ui.workbench/dialog_settings.xml
index 800e109..bf78e16 100644
--- a/.metadata/.plugins/org.eclipse.ui.workbench/dialog_settings.xml
+++ b/.metadata/.plugins/org.eclipse.ui.workbench/dialog_settings.xml
@@ -1,6 +1,10 @@
diff --git a/.metadata/version.ini b/.metadata/version.ini
index d71b56b..d428925 100644
--- a/.metadata/version.ini
+++ b/.metadata/version.ini
@@ -1,3 +1,3 @@
-#Mon Mar 23 15:59:12 CET 2026
+#Mon Mar 23 21:09:40 CET 2026
org.eclipse.core.runtime=2
org.eclipse.platform=4.39.0.v20260226-0420
diff --git a/bilder/toys/augenbinde.png b/bilder/toys/augenbinde.png
new file mode 100644
index 0000000..5fc4d5a
Binary files /dev/null and b/bilder/toys/augenbinde.png differ
diff --git a/bilder/toys/chastitycage.png b/bilder/toys/chastitycage.png
new file mode 100644
index 0000000..75d277a
Binary files /dev/null and b/bilder/toys/chastitycage.png differ
diff --git a/bilder/toys/dildo.png b/bilder/toys/dildo.png
new file mode 100644
index 0000000..cdb1ea0
Binary files /dev/null and b/bilder/toys/dildo.png differ
diff --git a/bilder/toys/dilliator.png b/bilder/toys/dilliator.png
new file mode 100644
index 0000000..c046665
Binary files /dev/null and b/bilder/toys/dilliator.png differ
diff --git a/bilder/toys/electro_plug.png b/bilder/toys/electro_plug.png
new file mode 100644
index 0000000..8089ed6
Binary files /dev/null and b/bilder/toys/electro_plug.png differ
diff --git a/bilder/toys/femaleCB.png b/bilder/toys/femaleCB.png
new file mode 100644
index 0000000..def74dc
Binary files /dev/null and b/bilder/toys/femaleCB.png differ
diff --git a/bilder/toys/gerte.png b/bilder/toys/gerte.png
new file mode 100644
index 0000000..75b7ffb
Binary files /dev/null and b/bilder/toys/gerte.png differ
diff --git a/bilder/toys/handfesseln.png b/bilder/toys/handfesseln.png
new file mode 100644
index 0000000..d37af2a
Binary files /dev/null and b/bilder/toys/handfesseln.png differ
diff --git a/bilder/toys/knebel.png b/bilder/toys/knebel.png
new file mode 100644
index 0000000..47c66fd
Binary files /dev/null and b/bilder/toys/knebel.png differ
diff --git a/bilder/toys/maleCB.png b/bilder/toys/maleCB.png
new file mode 100644
index 0000000..13cfd7d
Binary files /dev/null and b/bilder/toys/maleCB.png differ
diff --git a/bilder/toys/mastubator.png b/bilder/toys/mastubator.png
new file mode 100644
index 0000000..6349228
Binary files /dev/null and b/bilder/toys/mastubator.png differ
diff --git a/bilder/toys/nippelklemmen.png b/bilder/toys/nippelklemmen.png
new file mode 100644
index 0000000..a85c0d9
Binary files /dev/null and b/bilder/toys/nippelklemmen.png differ
diff --git a/bilder/toys/paddel.png b/bilder/toys/paddel.png
new file mode 100644
index 0000000..82b6f68
Binary files /dev/null and b/bilder/toys/paddel.png differ
diff --git a/bilder/toys/peitsche.png b/bilder/toys/peitsche.png
new file mode 100644
index 0000000..29f9950
Binary files /dev/null and b/bilder/toys/peitsche.png differ
diff --git a/bilder/toys/plugs.png b/bilder/toys/plugs.png
new file mode 100644
index 0000000..0aa9d64
Binary files /dev/null and b/bilder/toys/plugs.png differ
diff --git a/bilder/toys/pnsknebel.png b/bilder/toys/pnsknebel.png
new file mode 100644
index 0000000..c147659
Binary files /dev/null and b/bilder/toys/pnsknebel.png differ
diff --git a/bilder/toys/prostatavibrator.png b/bilder/toys/prostatavibrator.png
new file mode 100644
index 0000000..4821b88
Binary files /dev/null and b/bilder/toys/prostatavibrator.png differ
diff --git a/bilder/toys/pumpplug.png b/bilder/toys/pumpplug.png
new file mode 100644
index 0000000..ed55d95
Binary files /dev/null and b/bilder/toys/pumpplug.png differ
diff --git a/bilder/toys/seil.png b/bilder/toys/seil.png
new file mode 100644
index 0000000..bedc04d
Binary files /dev/null and b/bilder/toys/seil.png differ
diff --git a/bilder/toys/strapon.png b/bilder/toys/strapon.png
new file mode 100644
index 0000000..a7dc6ea
Binary files /dev/null and b/bilder/toys/strapon.png differ
diff --git a/bilder/toys/vibrator.png b/bilder/toys/vibrator.png
new file mode 100644
index 0000000..ff31440
Binary files /dev/null and b/bilder/toys/vibrator.png differ
diff --git a/testdaten.sql b/testdaten.sql
new file mode 100644
index 0000000..6fb7835
--- /dev/null
+++ b/testdaten.sql
@@ -0,0 +1,504 @@
+-- =============================================================
+-- XXX The Game – Testdaten
+-- =============================================================
+-- Passwort für alle User: Test1234!
+-- SHA-256("Test1234!") = 11a1162b984f8cf531e07d9bde6e27f26d6e9c0a2c4c52a6c1f0e2e79cd4e4a
+-- Hinweis: Login erwartet SHA-256-Hash vom Client
+-- =============================================================
+
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- Aufräumen (Reihenfolge wegen FK)
+DELETE FROM kommentar_like;
+DELETE FROM kommentar;
+DELETE FROM pinnwand_like;
+DELETE FROM pinnwand_eintrag;
+DELETE FROM feed_post_vote;
+DELETE FROM feed_post_option;
+DELETE FROM feed_post_like;
+DELETE FROM feed_post;
+DELETE FROM umfrage_stimme;
+DELETE FROM umfrage_option;
+DELETE FROM gruppe_beitrag_like;
+DELETE FROM gruppe_beitrag;
+DELETE FROM beitrittsanfrage;
+DELETE FROM gruppe_mitglied;
+DELETE FROM gruppe;
+DELETE FROM profile_image_like;
+DELETE FROM profile_image;
+DELETE FROM friendship;
+DELETE FROM registration;
+DELETE FROM `user`;
+
+SET FOREIGN_KEY_CHECKS = 1;
+
+-- =============================================================
+-- BENUTZER (5 User mit unterschiedlichen Profilen)
+-- =============================================================
+
+INSERT INTO `user` (
+ user_id, name, email, password, geburtsdatum,
+ groesse, gewicht, geschlecht, neigung, beziehungsstatus, beschreibung,
+ lockee_xp, keyholder_xp, bdsm_xp,
+ sichtbarkeit_grunddaten, sichtbarkeit_galerie, sichtbarkeit_freunde,
+ sichtbarkeit_feed, sichtbarkeit_pinnwand, sichtbarkeit_xp, sichtbarkeit_lockhistorie
+) VALUES
+
+-- 1. MaxMuster – dominant, Single
+('11111111-1111-1111-1111-000000000001',
+ 'MaxMuster', 'max@test.de',
+ '11a1162b984f8cf531e07d9bde6e27f26d6e9c0a2c4c52a6c1f0e2e79cd4e4a',
+ '1990-05-15',
+ 182, 80, 'MAENNLICH', 'DOMINANT', 'SINGLE',
+ 'Erfahrener Keyholder, der auf striktes aber faires Spiel steht. Immer offen für neue Spielpartner.',
+ 120, 850, 300,
+ 'ALLE', 'ALLE', 'ALLE', 'ALLE', 'ALLE', 'ALLE', 'ALLE'),
+
+-- 2. LisaLust – devot, Single
+('11111111-1111-1111-1111-000000000002',
+ 'LisaLust', 'lisa@test.de',
+ '11a1162b984f8cf531e07d9bde6e27f26d6e9c0a2c4c52a6c1f0e2e79cd4e4a',
+ '1995-08-22',
+ 165, 58, 'WEIBLICH', 'DEVOT', 'SINGLE',
+ 'Neugierigie Lockee auf der Suche nach einem verlässlichen Keyholder. Mag lange Sperren und herausfordernde Aufgaben.',
+ 740, 0, 150,
+ 'ALLE', 'NUR_FREUNDE', 'ALLE', 'ALLE', 'ALLE', 'ALLE', 'NUR_FREUNDE'),
+
+-- 3. SamSwitcher – Switcher, in Beziehung
+('11111111-1111-1111-1111-000000000003',
+ 'SamSwitcher', 'sam@test.de',
+ '11a1162b984f8cf531e07d9bde6e27f26d6e9c0a2c4c52a6c1f0e2e79cd4e4a',
+ '1988-11-03',
+ 175, 70, 'DIVERS', 'SWITCHER', 'IN_EINER_BEZIEHUNG',
+ 'Mal oben, mal unten – kommt auf die Stimmung an. Spiele gerne mit meinem Partner zusammen.',
+ 430, 390, 600,
+ 'ALLE', 'ALLE', 'ALLE', 'NUR_FREUNDE', 'ALLE', 'NUR_FREUNDE', 'ALLE'),
+
+-- 4. KajaKette – eher devot, Single
+('11111111-1111-1111-1111-000000000004',
+ 'KajaKette', 'kaja@test.de',
+ '11a1162b984f8cf531e07d9bde6e27f26d6e9c0a2c4c52a6c1f0e2e79cd4e4a',
+ '1998-02-14',
+ 170, 62, 'WEIBLICH', 'EHER_DEVOT', 'SINGLE',
+ 'Chastity-Enthusiastin mit Fokus auf Community-Locks. Schreibe gerne auf Pinnwände!',
+ 920, 50, 80,
+ 'ALLE', 'ALLE', 'ALLE', 'ALLE', 'ALLE', 'ALLE', 'ALLE'),
+
+-- 5. TomTop – eher dominant, verheiratet
+('11111111-1111-1111-1111-000000000005',
+ 'TomTop', 'tom@test.de',
+ '11a1162b984f8cf531e07d9bde6e27f26d6e9c0a2c4c52a6c1f0e2e79cd4e4a',
+ '1985-07-30',
+ 178, 85, 'MAENNLICH', 'EHER_DOMINANT', 'VERHEIRATET',
+ 'Verheiratet, spielen als Paar. Biete Keyholder-Service für seriöse Anfragen.',
+ 200, 560, 410,
+ 'ALLE', 'NUR_FREUNDE', 'NUR_FREUNDE', 'NUR_FREUNDE', 'ALLE', 'ALLE', 'NUR_FREUNDE');
+
+-- =============================================================
+-- NICHT AKTIVIERTE REGISTRIERUNG (für Registrierungs-Tests)
+-- =============================================================
+
+INSERT INTO registration (
+ registration_id, name, email, password, activated, activation_code, geburtsdatum
+) VALUES
+('99999999-9999-9999-9999-000000000001',
+ 'NeuerUser', 'neu@test.de',
+ '11a1162b984f8cf531e07d9bde6e27f26d6e9c0a2c4c52a6c1f0e2e79cd4e4a',
+ FALSE, '347821', '2000-01-01');
+
+-- =============================================================
+-- FREUNDSCHAFTEN
+-- =============================================================
+
+INSERT INTO friendship (friendship_id, sender_id, receiver_id, status, created_at) VALUES
+-- Max ↔ Lisa (akzeptiert)
+('22222222-2222-2222-2222-000000000001',
+ '11111111-1111-1111-1111-000000000001',
+ '11111111-1111-1111-1111-000000000002',
+ 'ACCEPTED', '2025-11-01 10:00:00'),
+-- Max ↔ Sam (akzeptiert)
+('22222222-2222-2222-2222-000000000002',
+ '11111111-1111-1111-1111-000000000001',
+ '11111111-1111-1111-1111-000000000003',
+ 'ACCEPTED', '2025-11-15 14:30:00'),
+-- Lisa ↔ Kaja (akzeptiert)
+('22222222-2222-2222-2222-000000000003',
+ '11111111-1111-1111-1111-000000000002',
+ '11111111-1111-1111-1111-000000000004',
+ 'ACCEPTED', '2025-12-03 09:15:00'),
+-- Tom → Kaja (ausstehend)
+('22222222-2222-2222-2222-000000000004',
+ '11111111-1111-1111-1111-000000000005',
+ '11111111-1111-1111-1111-000000000004',
+ 'PENDING', '2026-01-10 18:45:00'),
+-- Sam ↔ Kaja (akzeptiert)
+('22222222-2222-2222-2222-000000000005',
+ '11111111-1111-1111-1111-000000000003',
+ '11111111-1111-1111-1111-000000000004',
+ 'ACCEPTED', '2026-01-20 11:00:00');
+
+-- =============================================================
+-- PINNWAND-EINTRÄGE
+-- =============================================================
+
+INSERT INTO pinnwand_eintrag (eintrag_id, profil_user_id, author_id, text, created_at) VALUES
+-- Auf Lisas Pinnwand
+('33333333-3333-3333-3333-000000000001',
+ '11111111-1111-1111-1111-000000000002',
+ '11111111-1111-1111-1111-000000000001',
+ 'Hey Lisa! Schön, dich hier zu sehen. Viel Spaß beim Spielen 🔒',
+ '2025-12-10 16:00:00'),
+('33333333-3333-3333-3333-000000000002',
+ '11111111-1111-1111-1111-000000000002',
+ '11111111-1111-1111-1111-000000000004',
+ 'Wir sollten mal ein gemeinsames Lock starten! Meld dich 😊',
+ '2026-01-05 12:30:00'),
+-- Auf Maxs Pinnwand
+('33333333-3333-3333-3333-000000000003',
+ '11111111-1111-1111-1111-000000000001',
+ '11111111-1111-1111-1111-000000000002',
+ 'Danke für den tollen Keyholder-Service letzte Woche!',
+ '2026-01-08 20:00:00'),
+-- Auf Kajas Pinnwand
+('33333333-3333-3333-3333-000000000004',
+ '11111111-1111-1111-1111-000000000004',
+ '11111111-1111-1111-1111-000000000003',
+ 'Kaja, du bist die Community-Queen! Immer so aktiv hier.',
+ '2026-02-14 09:00:00');
+
+-- Pinnwand-Likes
+INSERT INTO pinnwand_like (like_id, eintrag_id, user_id, liked_at) VALUES
+('33333333-3333-3333-3333-000000000101',
+ '33333333-3333-3333-3333-000000000001',
+ '11111111-1111-1111-1111-000000000002',
+ '2025-12-10 16:05:00'),
+('33333333-3333-3333-3333-000000000102',
+ '33333333-3333-3333-3333-000000000002',
+ '11111111-1111-1111-1111-000000000001',
+ '2026-01-05 13:00:00'),
+('33333333-3333-3333-3333-000000000103',
+ '33333333-3333-3333-3333-000000000003',
+ '11111111-1111-1111-1111-000000000004',
+ '2026-01-09 10:00:00');
+
+-- =============================================================
+-- KOMMENTARE
+-- =============================================================
+
+INSERT INTO kommentar (kommentar_id, author_id, target_type, target_id, text, created_at) VALUES
+-- Kommentar auf Pinnwand-Eintrag
+('44444444-4444-4444-4444-000000000001',
+ '11111111-1111-1111-1111-000000000002',
+ 'PINNWAND',
+ '33333333-3333-3333-3333-000000000001',
+ 'Danke Max! Ich freu mich auch 😊',
+ '2025-12-10 17:00:00'),
+('44444444-4444-4444-4444-000000000002',
+ '11111111-1111-1111-1111-000000000003',
+ 'PINNWAND',
+ '33333333-3333-3333-3333-000000000001',
+ '+1, willkommen in der Community!',
+ '2025-12-10 18:30:00'),
+-- Reply auf Kommentar
+('44444444-4444-4444-4444-000000000003',
+ '11111111-1111-1111-1111-000000000001',
+ 'KOMMENTAR',
+ '44444444-4444-4444-4444-000000000001',
+ 'Na logo! Wir machen das 😄',
+ '2025-12-10 17:15:00');
+
+-- Kommentar-Likes
+INSERT INTO kommentar_like (like_id, kommentar_id, user_id, liked_at) VALUES
+('44444444-4444-4444-4444-000000000101',
+ '44444444-4444-4444-4444-000000000001',
+ '11111111-1111-1111-1111-000000000001',
+ '2025-12-10 17:10:00'),
+('44444444-4444-4444-4444-000000000102',
+ '44444444-4444-4444-4444-000000000002',
+ '11111111-1111-1111-1111-000000000002',
+ '2025-12-10 19:00:00');
+
+-- =============================================================
+-- FEED-POSTS (Text + Umfrage)
+-- =============================================================
+
+INSERT INTO feed_post (post_id, author_id, text, beitrag_typ, multi_choice, is_public, created_at) VALUES
+-- Öffentlicher Text-Post von Max
+('55555555-5555-5555-5555-000000000001',
+ '11111111-1111-1111-1111-000000000001',
+ 'Wer hat Lust auf ein Cardlock-Turnier nächsten Monat? Community vs. Keyholder! 🃏',
+ 'TEXT', NULL, TRUE, '2026-02-01 10:00:00'),
+
+-- Öffentlicher Text-Post von Lisa
+('55555555-5555-5555-5555-000000000002',
+ '11111111-1111-1111-1111-000000000002',
+ '48 Stunden geschafft! Das war mein bisher längstes Lock. Ich bin so stolz auf mich! 🔐✨',
+ 'TEXT', NULL, TRUE, '2026-02-05 14:30:00'),
+
+-- Öffentliche Umfrage von Kaja (Single-Choice)
+('55555555-5555-5555-5555-000000000003',
+ '11111111-1111-1111-1111-000000000004',
+ 'Was bevorzugt ihr: Cardlock oder Timelock?',
+ 'UMFRAGE', FALSE, TRUE, '2026-02-10 09:00:00'),
+
+-- Öffentliche Umfrage von Sam (Multi-Choice)
+('55555555-5555-5555-5555-000000000004',
+ '11111111-1111-1111-1111-000000000003',
+ 'Welche Features wollt ihr als nächstes sehen? (Mehrfachauswahl möglich)',
+ 'UMFRAGE', TRUE, TRUE, '2026-02-15 20:00:00'),
+
+-- Nicht-öffentlicher Post von Tom
+('55555555-5555-5555-5555-000000000005',
+ '11111111-1111-1111-1111-000000000005',
+ 'Spielen heute Abend mit meiner Frau eine Runde BDSM. Sie darf den Keyholder spielen!',
+ 'TEXT', NULL, FALSE, '2026-02-20 18:00:00');
+
+-- Umfrage-Optionen
+INSERT INTO feed_post_option (option_id, post_id, text, reihenfolge) VALUES
+-- Kajas Umfrage
+('55555555-5555-5555-5555-000000000101', '55555555-5555-5555-5555-000000000003', 'Cardlock – ich liebe die Ungewissheit!', 0),
+('55555555-5555-5555-5555-000000000102', '55555555-5555-5555-5555-000000000003', 'Timelock – Struktur ist alles.', 1),
+('55555555-5555-5555-5555-000000000103', '55555555-5555-5555-5555-000000000003', 'Beides gleich gerne.', 2),
+-- Sams Umfrage
+('55555555-5555-5555-5555-000000000104', '55555555-5555-5555-5555-000000000004', 'Mobile App', 0),
+('55555555-5555-5555-5555-000000000105', '55555555-5555-5555-5555-000000000004', 'Mehr Aufgaben-Vorlagen', 1),
+('55555555-5555-5555-5555-000000000106', '55555555-5555-5555-5555-000000000004', 'Dark/Light Theme Toggle', 2),
+('55555555-5555-5555-5555-000000000107', '55555555-5555-5555-5555-000000000004', 'Push-Benachrichtigungen', 3);
+
+-- Umfrage-Stimmen
+INSERT INTO feed_post_vote (stimme_id, option_id, post_id, user_id) VALUES
+-- Kajas Umfrage
+('55555555-5555-5555-5555-000000000201', '55555555-5555-5555-5555-000000000101', '55555555-5555-5555-5555-000000000003', '11111111-1111-1111-1111-000000000001'),
+('55555555-5555-5555-5555-000000000202', '55555555-5555-5555-5555-000000000101', '55555555-5555-5555-5555-000000000003', '11111111-1111-1111-1111-000000000002'),
+('55555555-5555-5555-5555-000000000203', '55555555-5555-5555-5555-000000000102', '55555555-5555-5555-5555-000000000003', '11111111-1111-1111-1111-000000000005'),
+('55555555-5555-5555-5555-000000000204', '55555555-5555-5555-5555-000000000103', '55555555-5555-5555-5555-000000000003', '11111111-1111-1111-1111-000000000003'),
+-- Sams Umfrage (Multi-Choice)
+('55555555-5555-5555-5555-000000000205', '55555555-5555-5555-5555-000000000104', '55555555-5555-5555-5555-000000000004', '11111111-1111-1111-1111-000000000001'),
+('55555555-5555-5555-5555-000000000206', '55555555-5555-5555-5555-000000000105', '55555555-5555-5555-5555-000000000004', '11111111-1111-1111-1111-000000000001'),
+('55555555-5555-5555-5555-000000000207', '55555555-5555-5555-5555-000000000104', '55555555-5555-5555-5555-000000000004', '11111111-1111-1111-1111-000000000002'),
+('55555555-5555-5555-5555-000000000208', '55555555-5555-5555-5555-000000000107', '55555555-5555-5555-5555-000000000004', '11111111-1111-1111-1111-000000000002'),
+('55555555-5555-5555-5555-000000000209', '55555555-5555-5555-5555-000000000105', '55555555-5555-5555-5555-000000000004', '11111111-1111-1111-1111-000000000004');
+
+-- Feed-Likes
+INSERT INTO feed_post_like (like_id, post_id, user_id, liked_at) VALUES
+('55555555-5555-5555-5555-000000000301', '55555555-5555-5555-5555-000000000001', '11111111-1111-1111-1111-000000000002', '2026-02-01 10:30:00'),
+('55555555-5555-5555-5555-000000000302', '55555555-5555-5555-5555-000000000001', '11111111-1111-1111-1111-000000000003', '2026-02-01 11:00:00'),
+('55555555-5555-5555-5555-000000000303', '55555555-5555-5555-5555-000000000001', '11111111-1111-1111-1111-000000000004', '2026-02-01 11:15:00'),
+('55555555-5555-5555-5555-000000000304', '55555555-5555-5555-5555-000000000002', '11111111-1111-1111-1111-000000000001', '2026-02-05 15:00:00'),
+('55555555-5555-5555-5555-000000000305', '55555555-5555-5555-5555-000000000002', '11111111-1111-1111-1111-000000000004', '2026-02-05 15:30:00'),
+('55555555-5555-5555-5555-000000000306', '55555555-5555-5555-5555-000000000002', '11111111-1111-1111-1111-000000000003', '2026-02-05 16:00:00');
+
+-- Kommentare unter Feed-Posts
+INSERT INTO kommentar (kommentar_id, author_id, target_type, target_id, text, created_at) VALUES
+('66666666-6666-6666-6666-000000000001',
+ '11111111-1111-1111-1111-000000000002',
+ 'FEED_POST',
+ '55555555-5555-5555-5555-000000000001',
+ 'Bin dabei! Wann genau? 🙋♀️',
+ '2026-02-01 11:00:00'),
+('66666666-6666-6666-6666-000000000002',
+ '11111111-1111-1111-1111-000000000003',
+ 'FEED_POST',
+ '55555555-5555-5555-5555-000000000001',
+ 'Klingt mega! Ich schlage vor: 1 Woche Mindestlaufzeit.',
+ '2026-02-01 11:30:00'),
+('66666666-6666-6666-6666-000000000003',
+ '11111111-1111-1111-1111-000000000001',
+ 'FEED_POST',
+ '55555555-5555-5555-5555-000000000002',
+ 'Respekt! 48h ist eine echte Leistung 👏',
+ '2026-02-05 15:00:00');
+
+-- =============================================================
+-- GRUPPEN
+-- =============================================================
+
+INSERT INTO gruppe (gruppe_id, name, beschreibung, bild, is_private, created_at, created_by_user_id) VALUES
+-- Öffentliche Gruppe
+('77777777-7777-7777-7777-000000000001',
+ 'Cardlock Community',
+ 'Die Gruppe für alle Cardlock-Fans! Hier tauschen wir Erfahrungen aus, veranstalten Turniere und helfen Neulingen beim Einstieg.',
+ NULL, FALSE, '2025-10-01 12:00:00',
+ '11111111-1111-1111-1111-000000000001'),
+
+-- Private Gruppe
+('77777777-7777-7777-7777-000000000002',
+ 'Keyholder-Stammtisch',
+ 'Privater Austausch unter erfahrenen Keyholdern. Nur auf Einladung.',
+ NULL, TRUE, '2025-11-15 18:00:00',
+ '11111111-1111-1111-1111-000000000005'),
+
+-- Öffentliche Gruppe
+('77777777-7777-7777-7777-000000000003',
+ 'Anfänger & Fragen',
+ 'Neuling? Frag einfach! Hier ist jede Frage willkommen. Keine Scheu.',
+ NULL, FALSE, '2026-01-01 00:00:00',
+ '11111111-1111-1111-1111-000000000004');
+
+-- =============================================================
+-- GRUPPENMITGLIEDER
+-- =============================================================
+
+INSERT INTO gruppe_mitglied (mitglied_id, gruppe_id, user_id, rolle, joined_at) VALUES
+-- Cardlock Community
+('77777777-7777-7777-7777-000000000101', '77777777-7777-7777-7777-000000000001', '11111111-1111-1111-1111-000000000001', 'ADMIN', '2025-10-01 12:00:00'),
+('77777777-7777-7777-7777-000000000102', '77777777-7777-7777-7777-000000000001', '11111111-1111-1111-1111-000000000002', 'MITGLIED', '2025-10-05 09:00:00'),
+('77777777-7777-7777-7777-000000000103', '77777777-7777-7777-7777-000000000001', '11111111-1111-1111-1111-000000000003', 'MITGLIED', '2025-10-10 14:00:00'),
+('77777777-7777-7777-7777-000000000104', '77777777-7777-7777-7777-000000000001', '11111111-1111-1111-1111-000000000004', 'MITGLIED', '2025-10-20 11:00:00'),
+-- Keyholder-Stammtisch
+('77777777-7777-7777-7777-000000000105', '77777777-7777-7777-7777-000000000002', '11111111-1111-1111-1111-000000000005', 'ADMIN', '2025-11-15 18:00:00'),
+('77777777-7777-7777-7777-000000000106', '77777777-7777-7777-7777-000000000002', '11111111-1111-1111-1111-000000000001', 'MITGLIED', '2025-11-20 10:00:00'),
+-- Anfänger & Fragen
+('77777777-7777-7777-7777-000000000107', '77777777-7777-7777-7777-000000000003', '11111111-1111-1111-1111-000000000004', 'ADMIN', '2026-01-01 00:00:00'),
+('77777777-7777-7777-7777-000000000108', '77777777-7777-7777-7777-000000000003', '11111111-1111-1111-1111-000000000002', 'MITGLIED', '2026-01-03 08:00:00'),
+('77777777-7777-7777-7777-000000000109', '77777777-7777-7777-7777-000000000003', '11111111-1111-1111-1111-000000000003', 'MITGLIED', '2026-01-05 12:00:00');
+
+-- Ausstehende Beitrittsanfrage zur privaten Gruppe
+INSERT INTO beitrittsanfrage (anfrage_id, gruppe_id, user_id, nachricht, angefragt_at, status) VALUES
+('77777777-7777-7777-7777-000000000201',
+ '77777777-7777-7777-7777-000000000002',
+ '11111111-1111-1111-1111-000000000003',
+ 'Hallo! Ich bin seit 2 Jahren aktiver Keyholder und würde gerne dazugehören.',
+ '2026-02-01 15:00:00', 'AUSSTEHEND'),
+('77777777-7777-7777-7777-000000000202',
+ '77777777-7777-7777-7777-000000000002',
+ '11111111-1111-1111-1111-000000000004',
+ 'Bitte nehmt mich auf! Habe schon ein paar Monate Erfahrung als Keyholderin.',
+ '2026-02-10 09:00:00', 'ABGELEHNT');
+
+-- =============================================================
+-- GRUPPEN-BEITRÄGE (Text + Umfrage)
+-- =============================================================
+
+INSERT INTO gruppe_beitrag (beitrag_id, gruppe_id, author_id, beitrag_typ, text, multi_choice, bild, created_at) VALUES
+-- Cardlock Community
+('88888888-8888-8888-8888-000000000001',
+ '77777777-7777-7777-7777-000000000001',
+ '11111111-1111-1111-1111-000000000001',
+ 'TEXT',
+ 'Willkommen in der Cardlock Community! Stellt euch kurz vor und erzählt, wie ihr zum Cardlock gekommen seid.',
+ NULL, NULL, '2025-10-01 12:05:00'),
+
+('88888888-8888-8888-8888-000000000002',
+ '77777777-7777-7777-7777-000000000001',
+ '11111111-1111-1111-1111-000000000002',
+ 'TEXT',
+ 'Ich bin Lisa und liebe Cardlocks seit über einem Jahr! Mein Rekord sind 5 Tage – habt ihr Tipps für längere Sperren?',
+ NULL, NULL, '2025-10-05 10:00:00'),
+
+('88888888-8888-8888-8888-000000000003',
+ '77777777-7777-7777-7777-000000000001',
+ '11111111-1111-1111-1111-000000000004',
+ 'UMFRAGE',
+ 'Wie viele Karten startet ihr typischerweise mit?',
+ FALSE, NULL, '2025-10-20 14:00:00'),
+
+-- Anfänger & Fragen
+('88888888-8888-8888-8888-000000000004',
+ '77777777-7777-7777-7777-000000000003',
+ '11111111-1111-1111-1111-000000000002',
+ 'TEXT',
+ 'Frage: Wie erkläre ich Cardlocks am besten meinem Partner, der noch nie davon gehört hat?',
+ NULL, NULL, '2026-01-10 19:00:00'),
+
+('88888888-8888-8888-8888-000000000005',
+ '77777777-7777-7777-7777-000000000003',
+ '11111111-1111-1111-1111-000000000001',
+ 'TEXT',
+ 'Gute Frage! Ich würde empfehlen, erst mit einem kurzen Timelock anzufangen. So kann der Partner das Grundkonzept verstehen, ohne direkt mit der Karten-Mechanik überfordert zu werden.',
+ NULL, NULL, '2026-01-10 19:30:00');
+
+-- Umfrage-Optionen für Gruppen-Beitrag
+INSERT INTO umfrage_option (option_id, beitrag_id, text, reihenfolge) VALUES
+('88888888-8888-8888-8888-000000000101', '88888888-8888-8888-8888-000000000003', 'Unter 20 Karten', 0),
+('88888888-8888-8888-8888-000000000102', '88888888-8888-8888-8888-000000000003', '20–40 Karten', 1),
+('88888888-8888-8888-8888-000000000103', '88888888-8888-8888-8888-000000000003', '40–60 Karten', 2),
+('88888888-8888-8888-8888-000000000104', '88888888-8888-8888-8888-000000000003', 'Über 60 Karten', 3);
+
+-- Umfrage-Stimmen (Gruppen)
+INSERT INTO umfrage_stimme (stimme_id, option_id, beitrag_id, user_id) VALUES
+('88888888-8888-8888-8888-000000000201', '88888888-8888-8888-8888-000000000101', '88888888-8888-8888-8888-000000000003', '11111111-1111-1111-1111-000000000002'),
+('88888888-8888-8888-8888-000000000202', '88888888-8888-8888-8888-000000000102', '88888888-8888-8888-8888-000000000003', '11111111-1111-1111-1111-000000000001'),
+('88888888-8888-8888-8888-000000000203', '88888888-8888-8888-8888-000000000102', '88888888-8888-8888-8888-000000000003', '11111111-1111-1111-1111-000000000003'),
+('88888888-8888-8888-8888-000000000204', '88888888-8888-8888-8888-000000000103', '88888888-8888-8888-8888-000000000003', '11111111-1111-1111-1111-000000000004');
+
+-- Gruppen-Beitrag-Likes
+INSERT INTO gruppe_beitrag_like (like_id, beitrag_id, user_id, liked_at) VALUES
+('88888888-8888-8888-8888-000000000301', '88888888-8888-8888-8888-000000000001', '11111111-1111-1111-1111-000000000002', '2025-10-01 12:10:00'),
+('88888888-8888-8888-8888-000000000302', '88888888-8888-8888-8888-000000000001', '11111111-1111-1111-1111-000000000003', '2025-10-01 13:00:00'),
+('88888888-8888-8888-8888-000000000303', '88888888-8888-8888-8888-000000000001', '11111111-1111-1111-1111-000000000004', '2025-10-01 14:00:00'),
+('88888888-8888-8888-8888-000000000304', '88888888-8888-8888-8888-000000000002', '11111111-1111-1111-1111-000000000001', '2025-10-05 10:30:00'),
+('88888888-8888-8888-8888-000000000305', '88888888-8888-8888-8888-000000000002', '11111111-1111-1111-1111-000000000004', '2025-10-05 11:00:00'),
+('88888888-8888-8888-8888-000000000306', '88888888-8888-8888-8888-000000000005', '11111111-1111-1111-1111-000000000002', '2026-01-10 19:45:00'),
+('88888888-8888-8888-8888-000000000307', '88888888-8888-8888-8888-000000000005', '11111111-1111-1111-1111-000000000004', '2026-01-10 20:00:00');
+
+-- Kommentare auf Gruppen-Beiträge
+INSERT INTO kommentar (kommentar_id, author_id, target_type, target_id, text, created_at) VALUES
+('99999999-0000-0000-0000-000000000001',
+ '11111111-1111-1111-1111-000000000003',
+ 'GROUP_POST',
+ '88888888-8888-8888-8888-000000000002',
+ 'Hi Lisa! Mein Tipp: Fang mit mehr Green Cards an als du denkst. Du wirst es brauchen 😄',
+ '2025-10-05 11:00:00'),
+('99999999-0000-0000-0000-000000000002',
+ '11111111-1111-1111-1111-000000000001',
+ 'GROUP_POST',
+ '88888888-8888-8888-8888-000000000002',
+ 'Mentale Vorbereitung ist alles. Schreib dir vorher auf, warum du es tust.',
+ '2025-10-05 12:00:00'),
+('99999999-0000-0000-0000-000000000003',
+ '11111111-1111-1111-1111-000000000004',
+ 'GROUP_POST',
+ '88888888-8888-8888-8888-000000000004',
+ 'Ich würde sagen: zeig ihm/ihr einfach die App! Das visuelle Konzept erklärt sich fast von selbst.',
+ '2026-01-10 19:15:00');
+
+-- =============================================================
+-- CHASTITY LOCK (ein aktives Cardlock: Lisa gesperrt von Max)
+-- =============================================================
+
+INSERT INTO current_lock (
+ lock_id, lock_type, name, lockee, keyholder,
+ test_lock, requires_verification,
+ unlock_code_length, unlock_code,
+ start_time, unlock_time,
+ hygine_opening_duration_minutes, hygine_opening_everyminites,
+ task_mode,
+ keyholder_requested_unlock, emergency_auto_unlocked,
+ -- CARDLOCK-spezifisch
+ initial_cards, pick_every_minute, accumulate_picks,
+ show_remaining_cards, open_picks,
+ available_cards
+) VALUES (
+ 'aaaaaaaa-aaaa-aaaa-aaaa-000000000001',
+ 'CARDLOCK',
+ 'Lisas Frühlings-Lock',
+ '11111111-1111-1111-1111-000000000002', -- lockee: Lisa
+ '11111111-1111-1111-1111-000000000001', -- keyholder: Max
+ FALSE, FALSE,
+ 6, NULL,
+ '2026-03-20 10:00:00', NULL,
+ 30, 1440, -- Hygiene alle 24h, 30 Min offen
+ 'KEYHOLDER',
+ FALSE, FALSE,
+ -- 30 Karten: 5×GREEN, 15×RED, 5×YELLOW, 3×TASK, 2×FREEZE
+ '["GREEN","GREEN","GREEN","GREEN","GREEN","RED","RED","RED","RED","RED","RED","RED","RED","RED","RED","RED","RED","RED","RED","RED","YELLOW","YELLOW","YELLOW","YELLOW","YELLOW","TASK","TASK","TASK","FREEZE","FREEZE"]',
+ 240, FALSE, -- Karte alle 4h ziehen, kein Akkumulieren
+ TRUE, 0,
+ '["GREEN","GREEN","GREEN","GREEN","GREEN","RED","RED","RED","RED","RED","RED","RED","RED","RED","RED","RED","RED","RED","RED","RED","YELLOW","YELLOW","YELLOW","YELLOW","YELLOW","TASK","TASK","TASK","FREEZE","FREEZE"]'
+);
+
+-- =============================================================
+-- FERTIG
+-- =============================================================
+-- Überblick:
+-- 5 User (max@test.de, lisa@test.de, sam@test.de, kaja@test.de, tom@test.de)
+-- 1 nicht aktivierte Registrierung (neu@test.de, Code: 347821)
+-- 5 Freundschaften (4 akzeptiert, 1 ausstehend)
+-- 4 Pinnwand-Einträge + 3 Likes
+-- 3 Kommentare auf Pinnwand + 3 auf Feed + 3 auf Gruppen-Beiträge
+-- 5 Feed-Posts (3 Text, 2 Umfragen) + 6 Likes
+-- 3 Gruppen (2 öffentlich, 1 privat) mit je 4-6 Mitgliedern
+-- 5 Gruppen-Beiträge (4 Text, 1 Umfrage) + 7 Likes
+-- 1 aktives Cardlock (Lisa ← Max)
+-- =============================================================
diff --git a/testdaten/aufgabengruppen-export.zip b/testdaten/aufgabengruppen-export.zip
new file mode 100644
index 0000000..845f9ce
Binary files /dev/null and b/testdaten/aufgabengruppen-export.zip differ
diff --git a/testdaten/toys-export.zip b/testdaten/toys-export.zip
new file mode 100644
index 0000000..370854f
Binary files /dev/null and b/testdaten/toys-export.zip differ
diff --git a/xxxthegame/src/main/java/de/oaa/xxx/admin/AdminController.java b/xxxthegame/src/main/java/de/oaa/xxx/admin/AdminController.java
new file mode 100644
index 0000000..b7b72f0
--- /dev/null
+++ b/xxxthegame/src/main/java/de/oaa/xxx/admin/AdminController.java
@@ -0,0 +1,270 @@
+package de.oaa.xxx.admin;
+
+import de.oaa.xxx.aufgaben.AufgabenGruppe;
+import de.oaa.xxx.aufgaben.Toy;
+import de.oaa.xxx.aufgaben.entity.AufgabenGruppeEntity;
+import de.oaa.xxx.aufgaben.entity.ToyEntity;
+import de.oaa.xxx.aufgaben.repository.AufgabenGruppeRepository;
+import de.oaa.xxx.aufgaben.repository.ToyRepository;
+import de.oaa.xxx.meldung.MeldungEntity;
+import de.oaa.xxx.meldung.MeldungRepository;
+import de.oaa.xxx.meldung.MeldungStatus;
+import de.oaa.xxx.user.UserEntity;
+import de.oaa.xxx.user.UserRepository;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.http.ResponseEntity;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+
+import java.security.Principal;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.UUID;
+
+@RestController
+@RequestMapping("/admin")
+@Transactional
+public class AdminController {
+
+ private final AdminRepository adminRepository;
+ private final UserRepository userRepository;
+ private final MeldungRepository meldungRepository;
+ private final AufgabenGruppeRepository aufgabenGruppeRepository;
+ private final ToyRepository toyRepository;
+
+ public AdminController(AdminRepository adminRepository, UserRepository userRepository,
+ MeldungRepository meldungRepository,
+ AufgabenGruppeRepository aufgabenGruppeRepository,
+ ToyRepository toyRepository) {
+ this.adminRepository = adminRepository;
+ this.userRepository = userRepository;
+ this.meldungRepository = meldungRepository;
+ this.aufgabenGruppeRepository = aufgabenGruppeRepository;
+ this.toyRepository = toyRepository;
+ }
+
+ // ── DTOs ─────────────────────────────────────────────────────────────────
+
+ record AdminDto(UUID adminId, UUID userId, String userName, AdminRolle rolle, LocalDateTime createdAt) {}
+
+ record MeldungDto(UUID meldungId, UUID melderId, String melderName,
+ de.oaa.xxx.meldung.MeldungZielTyp zielTyp, UUID zielId,
+ String grund, LocalDateTime gemeldetAt,
+ MeldungStatus status, UUID bearbeitetVon, LocalDateTime bearbeitetAt) {}
+
+ record CreateAdminRequest(UUID userId, AdminRolle rolle) {}
+
+ record StatusRequest(MeldungStatus status) {}
+
+ // ── Hilfsmethoden ────────────────────────────────────────────────────────
+
+ private AdminEntity requireAdmin(Principal principal) {
+ var user = userRepository.findByEmail(principal.getName()).orElseThrow();
+ return adminRepository.findByUserId(user.getUserId())
+ .orElseThrow(() -> new org.springframework.web.server.ResponseStatusException(
+ org.springframework.http.HttpStatus.FORBIDDEN, "Kein Admin"));
+ }
+
+ private AdminEntity requireSuperAdmin(Principal principal) {
+ AdminEntity admin = requireAdmin(principal);
+ if (admin.getRolle() != AdminRolle.SUPERADMIN) {
+ throw new org.springframework.web.server.ResponseStatusException(
+ org.springframework.http.HttpStatus.FORBIDDEN, "Kein Superadmin");
+ }
+ return admin;
+ }
+
+ private AdminDto toDto(AdminEntity e) {
+ String name = userRepository.findById(e.getUserId()).map(UserEntity::getName).orElse("?");
+ return new AdminDto(e.getAdminId(), e.getUserId(), name, e.getRolle(), e.getCreatedAt());
+ }
+
+ private MeldungDto toMeldungDto(MeldungEntity e) {
+ String melderName = userRepository.findById(e.getMelderId()).map(UserEntity::getName).orElse("?");
+ return new MeldungDto(e.getMeldungId(), e.getMelderId(), melderName,
+ e.getZielTyp(), e.getZielId(), e.getGrund(), e.getGemeldetAt(),
+ e.getStatus(), e.getBearbeitetVon(), e.getBearbeitetAt());
+ }
+
+ // ── /admin/me ────────────────────────────────────────────────────────────
+
+ @GetMapping("/me")
+ public ResponseEntity me(Principal principal) {
+ var user = userRepository.findByEmail(principal.getName()).orElse(null);
+ if (user == null) return ResponseEntity.status(403).build();
+ return adminRepository.findByUserId(user.getUserId())
+ .map(a -> ResponseEntity.ok(toDto(a)))
+ .orElse(ResponseEntity.status(403).build());
+ }
+
+ // ── Meldungen ────────────────────────────────────────────────────────────
+
+ @GetMapping("/meldungen")
+ public ResponseEntity> getMeldungen(
+ @RequestParam(name = "status", required = false) MeldungStatus status,
+ Principal principal) {
+ requireAdmin(principal);
+ List list = status != null
+ ? meldungRepository.findByStatusOrderByGemeldetAtDesc(status)
+ : meldungRepository.findAllByOrderByGemeldetAtDesc();
+ return ResponseEntity.ok(list.stream().map(this::toMeldungDto).toList());
+ }
+
+ @PutMapping("/meldungen/{id}")
+ public ResponseEntity updateMeldung(@PathVariable("id") UUID id,
+ @RequestBody StatusRequest body,
+ Principal principal) {
+ var admin = requireAdmin(principal);
+ var user = userRepository.findByEmail(principal.getName()).orElseThrow();
+ MeldungEntity meldung = meldungRepository.findById(id)
+ .orElseThrow(() -> new org.springframework.web.server.ResponseStatusException(
+ org.springframework.http.HttpStatus.NOT_FOUND));
+ meldung.setStatus(body.status());
+ meldung.setBearbeitetVon(user.getUserId());
+ meldung.setBearbeitetAt(LocalDateTime.now());
+ return ResponseEntity.noContent().build();
+ }
+
+ // ── Aufgabengruppen ──────────────────────────────────────────────────────
+
+ @GetMapping("/aufgabengruppen")
+ public ResponseEntity> getAufgabengruppen(Principal principal) {
+ requireAdmin(principal);
+ List list = aufgabenGruppeRepository
+ .findByUserIdIsNull(PageRequest.of(0, 1000)).getContent();
+ return ResponseEntity.ok(list.stream().map(AufgabenGruppeEntity::toAufgabenGruppe).toList());
+ }
+
+ @PostMapping("/aufgabengruppen")
+ public ResponseEntity createAufgabengruppe(
+ @RequestBody AufgabenGruppe gruppe, Principal principal) {
+ requireAdmin(principal);
+ gruppe.setUserId(null);
+ gruppe.setPrivateGruppe(false);
+ AufgabenGruppeEntity entity = AufgabenGruppeEntity.create(gruppe);
+ aufgabenGruppeRepository.save(entity);
+ return ResponseEntity.status(201).body(entity.toAufgabenGruppeDisplay());
+ }
+
+ @PutMapping("/aufgabengruppen/{id}")
+ public ResponseEntity updateAufgabengruppe(@PathVariable("id") UUID id,
+ @RequestBody AufgabenGruppe gruppe,
+ Principal principal) {
+ requireAdmin(principal);
+ AufgabenGruppeEntity entity = aufgabenGruppeRepository.findById(id)
+ .orElseThrow(() -> new org.springframework.web.server.ResponseStatusException(
+ org.springframework.http.HttpStatus.NOT_FOUND));
+ entity.setName(gruppe.getName());
+ entity.setBeschreibung(gruppe.getBeschreibung());
+ entity.setVon(gruppe.getVon());
+ if (gruppe.getBild() != null) {
+ entity.setBild(java.util.Base64.getDecoder().decode(gruppe.getBild()));
+ }
+ return ResponseEntity.noContent().build();
+ }
+
+ @DeleteMapping("/aufgabengruppen/{id}")
+ public ResponseEntity deleteAufgabengruppe(@PathVariable("id") UUID id, Principal principal) {
+ requireAdmin(principal);
+ AufgabenGruppeEntity entity = aufgabenGruppeRepository.findById(id)
+ .orElseThrow(() -> new org.springframework.web.server.ResponseStatusException(
+ org.springframework.http.HttpStatus.NOT_FOUND));
+ if (entity.getUserId() != null) {
+ return ResponseEntity.status(403).build(); // Nur System-Gruppen
+ }
+ aufgabenGruppeRepository.delete(entity);
+ return ResponseEntity.noContent().build();
+ }
+
+ // ── Toys ─────────────────────────────────────────────────────────────────
+
+ @GetMapping("/toys")
+ public ResponseEntity> getToys(Principal principal) {
+ requireAdmin(principal);
+ List list = toyRepository.findByUserIdIsNull(PageRequest.of(0, 1000, Sort.by(Sort.Direction.ASC, "name"))).getContent();
+ return ResponseEntity.ok(list.stream().map(ToyEntity::toToy).toList());
+ }
+
+ @PostMapping("/toys")
+ public ResponseEntity createToy(@RequestBody Toy toy, Principal principal) {
+ requireAdmin(principal);
+ if (toyRepository.existsByNameIgnoreCaseAndUserIdIsNull(toy.getName())) {
+ return ResponseEntity.status(409).build();
+ }
+ toy.setUserId(null);
+ ToyEntity entity = ToyEntity.create(toy);
+ toyRepository.save(entity);
+ return ResponseEntity.status(201).body(entity.toToy());
+ }
+
+ @PutMapping("/toys/{id}")
+ public ResponseEntity updateToy(@PathVariable("id") UUID id,
+ @RequestBody Toy toy, Principal principal) {
+ requireAdmin(principal);
+ ToyEntity entity = toyRepository.findById(id)
+ .orElseThrow(() -> new org.springframework.web.server.ResponseStatusException(
+ org.springframework.http.HttpStatus.NOT_FOUND));
+ if (toyRepository.existsByNameIgnoreCaseAndUserIdIsNullAndToyIdNot(toy.getName(), id)) {
+ return ResponseEntity.status(409).build();
+ }
+ entity.setName(toy.getName());
+ entity.setBeschreibung(toy.getBeschreibung());
+ if (toy.getBild() != null) {
+ entity.setBild(java.util.Base64.getDecoder().decode(toy.getBild()));
+ }
+ return ResponseEntity.noContent().build();
+ }
+
+ @DeleteMapping("/toys/{id}")
+ public ResponseEntity deleteToy(@PathVariable("id") UUID id, Principal principal) {
+ requireAdmin(principal);
+ ToyEntity entity = toyRepository.findById(id)
+ .orElseThrow(() -> new org.springframework.web.server.ResponseStatusException(
+ org.springframework.http.HttpStatus.NOT_FOUND));
+ long usage = toyRepository.countAufgabeUsage(id)
+ + toyRepository.countStrafeUsage(id)
+ + toyRepository.countSperreUsage(id);
+ if (usage > 0) {
+ return ResponseEntity.status(409).build();
+ }
+ toyRepository.delete(entity);
+ return ResponseEntity.noContent().build();
+ }
+
+ // ── Admin-Verwaltung (nur SUPERADMIN) ────────────────────────────────────
+
+ @GetMapping("/admins")
+ public ResponseEntity> getAdmins(Principal principal) {
+ requireSuperAdmin(principal);
+ return ResponseEntity.ok(adminRepository.findAll().stream().map(this::toDto).toList());
+ }
+
+ @PostMapping("/admins")
+ public ResponseEntity createAdmin(@RequestBody CreateAdminRequest request, Principal principal) {
+ requireSuperAdmin(principal);
+ if (!userRepository.existsById(request.userId())) {
+ return ResponseEntity.status(404).build();
+ }
+ if (adminRepository.existsByUserId(request.userId())) {
+ return ResponseEntity.status(409).build();
+ }
+ AdminEntity entity = AdminEntity.create(request.userId(), request.rolle());
+ adminRepository.save(entity);
+ return ResponseEntity.status(201).body(toDto(entity));
+ }
+
+ @DeleteMapping("/admins/{id}")
+ public ResponseEntity deleteAdmin(@PathVariable("id") UUID id, Principal principal) {
+ var requestingUser = userRepository.findByEmail(principal.getName()).orElseThrow();
+ requireSuperAdmin(principal);
+ AdminEntity entity = adminRepository.findById(id)
+ .orElseThrow(() -> new org.springframework.web.server.ResponseStatusException(
+ org.springframework.http.HttpStatus.NOT_FOUND));
+ if (entity.getUserId().equals(requestingUser.getUserId())) {
+ return ResponseEntity.status(400).build(); // Selbstlöschung verhindern
+ }
+ adminRepository.delete(entity);
+ return ResponseEntity.noContent().build();
+ }
+}
diff --git a/xxxthegame/src/main/java/de/oaa/xxx/admin/AdminEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/admin/AdminEntity.java
new file mode 100644
index 0000000..9088698
--- /dev/null
+++ b/xxxthegame/src/main/java/de/oaa/xxx/admin/AdminEntity.java
@@ -0,0 +1,38 @@
+package de.oaa.xxx.admin;
+
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "admin")
+public class AdminEntity {
+
+ @Id
+ @Column
+ private UUID adminId;
+
+ @Column(nullable = false)
+ private UUID userId;
+
+ @Enumerated(EnumType.STRING)
+ @Column(length = 20, nullable = false)
+ private AdminRolle rolle;
+
+ @Column(nullable = false)
+ private LocalDateTime createdAt;
+
+ public static AdminEntity create(UUID userId, AdminRolle rolle) {
+ AdminEntity entity = new AdminEntity();
+ entity.setAdminId(UUID.randomUUID());
+ entity.setUserId(userId);
+ entity.setRolle(rolle);
+ entity.setCreatedAt(LocalDateTime.now());
+ return entity;
+ }
+}
diff --git a/xxxthegame/src/main/java/de/oaa/xxx/admin/AdminRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/admin/AdminRepository.java
new file mode 100644
index 0000000..1a3f831
--- /dev/null
+++ b/xxxthegame/src/main/java/de/oaa/xxx/admin/AdminRepository.java
@@ -0,0 +1,13 @@
+package de.oaa.xxx.admin;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+import java.util.UUID;
+
+public interface AdminRepository extends JpaRepository {
+
+ Optional findByUserId(UUID userId);
+
+ boolean existsByUserId(UUID userId);
+}
diff --git a/xxxthegame/src/main/java/de/oaa/xxx/admin/AdminRolle.java b/xxxthegame/src/main/java/de/oaa/xxx/admin/AdminRolle.java
new file mode 100644
index 0000000..18d2f22
--- /dev/null
+++ b/xxxthegame/src/main/java/de/oaa/xxx/admin/AdminRolle.java
@@ -0,0 +1,5 @@
+package de.oaa.xxx.admin;
+
+public enum AdminRolle {
+ ADMIN, SUPERADMIN
+}
diff --git a/xxxthegame/src/main/java/de/oaa/xxx/config/SecurityConfig.java b/xxxthegame/src/main/java/de/oaa/xxx/config/SecurityConfig.java
index 3db8e43..6a2c5d1 100644
--- a/xxxthegame/src/main/java/de/oaa/xxx/config/SecurityConfig.java
+++ b/xxxthegame/src/main/java/de/oaa/xxx/config/SecurityConfig.java
@@ -61,6 +61,7 @@ public class SecurityConfig {
.requestMatchers("/gruppen.html").authenticated()
.requestMatchers("/gruppe.html").authenticated()
.requestMatchers("/feed.html").authenticated()
+ .requestMatchers("/admin.html").authenticated()
.requestMatchers("/communityvotes.html").authenticated()
.requestMatchers("/keyholder.html").authenticated()
.requestMatchers("/meine-locks.html").authenticated()
diff --git a/xxxthegame/src/main/java/de/oaa/xxx/meldung/MeldungController.java b/xxxthegame/src/main/java/de/oaa/xxx/meldung/MeldungController.java
new file mode 100644
index 0000000..0bc33a8
--- /dev/null
+++ b/xxxthegame/src/main/java/de/oaa/xxx/meldung/MeldungController.java
@@ -0,0 +1,35 @@
+package de.oaa.xxx.meldung;
+
+import de.oaa.xxx.user.UserRepository;
+import org.springframework.http.ResponseEntity;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+
+import java.security.Principal;
+import java.util.UUID;
+
+@RestController
+@RequestMapping("/meldung")
+public class MeldungController {
+
+ private final MeldungRepository meldungRepository;
+ private final UserRepository userRepository;
+
+ public MeldungController(MeldungRepository meldungRepository, UserRepository userRepository) {
+ this.meldungRepository = meldungRepository;
+ this.userRepository = userRepository;
+ }
+
+ record MeldungRequest(MeldungZielTyp zielTyp, UUID zielId, String grund) {}
+
+ @PostMapping
+ @Transactional
+ public ResponseEntity melden(@RequestBody MeldungRequest request, Principal principal) {
+ var user = userRepository.findByEmail(principal.getName()).orElseThrow();
+ if (meldungRepository.existsByMelderIdAndZielTypAndZielId(user.getUserId(), request.zielTyp(), request.zielId())) {
+ return ResponseEntity.status(409).build();
+ }
+ meldungRepository.save(MeldungEntity.create(user.getUserId(), request.zielTyp(), request.zielId(), request.grund()));
+ return ResponseEntity.status(201).build();
+ }
+}
diff --git a/xxxthegame/src/main/java/de/oaa/xxx/meldung/MeldungEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/meldung/MeldungEntity.java
new file mode 100644
index 0000000..56ba75c
--- /dev/null
+++ b/xxxthegame/src/main/java/de/oaa/xxx/meldung/MeldungEntity.java
@@ -0,0 +1,59 @@
+package de.oaa.xxx.meldung;
+
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "meldung", uniqueConstraints = {
+ @UniqueConstraint(columnNames = {"melder_id", "ziel_typ", "ziel_id"})
+})
+public class MeldungEntity {
+
+ @Id
+ @Column
+ private UUID meldungId;
+
+ @Column(name = "melder_id", nullable = false)
+ private UUID melderId;
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "ziel_typ", length = 10, nullable = false)
+ private MeldungZielTyp zielTyp;
+
+ @Column(name = "ziel_id", nullable = false)
+ private UUID zielId;
+
+ @Column(columnDefinition = "TEXT")
+ private String grund;
+
+ @Column(nullable = false)
+ private LocalDateTime gemeldetAt;
+
+ @Enumerated(EnumType.STRING)
+ @Column(length = 20, nullable = false)
+ private MeldungStatus status;
+
+ @Column(name = "bearbeitet_von")
+ private UUID bearbeitetVon;
+
+ @Column(name = "bearbeitet_at")
+ private LocalDateTime bearbeitetAt;
+
+ public static MeldungEntity create(UUID melderId, MeldungZielTyp zielTyp, UUID zielId, String grund) {
+ MeldungEntity entity = new MeldungEntity();
+ entity.setMeldungId(UUID.randomUUID());
+ entity.setMelderId(melderId);
+ entity.setZielTyp(zielTyp);
+ entity.setZielId(zielId);
+ entity.setGrund(grund);
+ entity.setGemeldetAt(LocalDateTime.now());
+ entity.setStatus(MeldungStatus.OFFEN);
+ return entity;
+ }
+}
diff --git a/xxxthegame/src/main/java/de/oaa/xxx/meldung/MeldungRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/meldung/MeldungRepository.java
new file mode 100644
index 0000000..77c383d
--- /dev/null
+++ b/xxxthegame/src/main/java/de/oaa/xxx/meldung/MeldungRepository.java
@@ -0,0 +1,15 @@
+package de.oaa.xxx.meldung;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+import java.util.UUID;
+
+public interface MeldungRepository extends JpaRepository {
+
+ List findAllByOrderByGemeldetAtDesc();
+
+ List findByStatusOrderByGemeldetAtDesc(MeldungStatus status);
+
+ boolean existsByMelderIdAndZielTypAndZielId(UUID melderId, MeldungZielTyp zielTyp, UUID zielId);
+}
diff --git a/xxxthegame/src/main/java/de/oaa/xxx/meldung/MeldungStatus.java b/xxxthegame/src/main/java/de/oaa/xxx/meldung/MeldungStatus.java
new file mode 100644
index 0000000..2bd6bfd
--- /dev/null
+++ b/xxxthegame/src/main/java/de/oaa/xxx/meldung/MeldungStatus.java
@@ -0,0 +1,5 @@
+package de.oaa.xxx.meldung;
+
+public enum MeldungStatus {
+ OFFEN, BEARBEITET, ABGELEHNT
+}
diff --git a/xxxthegame/src/main/java/de/oaa/xxx/meldung/MeldungZielTyp.java b/xxxthegame/src/main/java/de/oaa/xxx/meldung/MeldungZielTyp.java
new file mode 100644
index 0000000..2af7eb4
--- /dev/null
+++ b/xxxthegame/src/main/java/de/oaa/xxx/meldung/MeldungZielTyp.java
@@ -0,0 +1,5 @@
+package de.oaa.xxx.meldung;
+
+public enum MeldungZielTyp {
+ POST, PROFIL
+}
diff --git a/xxxthegame/src/main/java/de/oaa/xxx/registration/ActivationController.java b/xxxthegame/src/main/java/de/oaa/xxx/registration/ActivationController.java
index 6572e61..1a3210c 100644
--- a/xxxthegame/src/main/java/de/oaa/xxx/registration/ActivationController.java
+++ b/xxxthegame/src/main/java/de/oaa/xxx/registration/ActivationController.java
@@ -19,7 +19,7 @@ public class ActivationController {
}
@GetMapping("/{uuid}")
- public ResponseEntity activate(@PathVariable String uuid) {
+ public ResponseEntity activate(@PathVariable("uuid") String uuid) {
try {
String email = registrationService.activate(uuid);
String redirect = "/login.html?email=" + java.net.URLEncoder.encode(email, java.nio.charset.StandardCharsets.UTF_8);
diff --git a/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationController.java b/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationController.java
index 4aea067..dc95b9d 100644
--- a/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationController.java
+++ b/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationController.java
@@ -51,16 +51,24 @@ public class RegistrationController {
LOGGER.warn("Registrierung abgelehnt – Mindestalter nicht erreicht");
return ResponseEntity.status(422).build();
}
- if (registrationRepository.findByEmail(registration.getEmail()).isPresent()
- || userRepository.findByEmail(registration.getEmail()).isPresent()) {
+ // Bereits aktivierte User blockieren
+ if (userRepository.findByEmail(registration.getEmail()).isPresent()) {
LOGGER.warn("User mit E-Mail {} bereits vorhanden", registration.getEmail());
return ResponseEntity.badRequest().build();
}
- if (registrationRepository.findByName(registration.getName()).isPresent()
- || userRepository.findByName(registration.getName()).isPresent()) {
+ if (userRepository.findByName(registration.getName()).isPresent()) {
LOGGER.warn("User mit Name {} bereits vorhanden", registration.getName());
return ResponseEntity.status(409).build();
}
+ // Noch nicht aktivierte Registrierungen mit gleicher E-Mail oder Name überschreiben
+ registrationRepository.findByEmail(registration.getEmail()).ifPresent(old -> {
+ LOGGER.info("Überschreibe nicht aktivierte Registrierung mit E-Mail {}", registration.getEmail());
+ registrationRepository.delete(old);
+ });
+ registrationRepository.findByName(registration.getName()).ifPresent(old -> {
+ LOGGER.info("Überschreibe nicht aktivierte Registrierung mit Name {}", registration.getName());
+ registrationRepository.delete(old);
+ });
// Passwort serverseitig mit BCrypt hashen
registration.setPassword(passwordEncoder.encode(registration.getPassword()));
RegistrationEntity entity = RegistrationEntity.create(registration);
@@ -72,7 +80,7 @@ public class RegistrationController {
String uuid = entity.getRegistrationId().toString();
String activationLink = baseUrl + "/activation/" + uuid;
String activatePageUrl = baseUrl + "/activate.html";
- email.setText(mailTemplateService.buildActivationMail(registration.getName(), activationLink, activatePageUrl, uuid));
+ email.setText(mailTemplateService.buildActivationMail(registration.getName(), activationLink, activatePageUrl, entity.getActivationCode()));
if (!mailService.send(email)) {
registrationRepository.delete(entity);
diff --git a/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationEntity.java
index eee1a6f..ceed773 100644
--- a/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationEntity.java
+++ b/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationEntity.java
@@ -7,6 +7,7 @@ import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
+import java.security.SecureRandom;
import java.time.LocalDate;
import java.util.UUID;
@@ -27,6 +28,8 @@ public class RegistrationEntity {
private String password;
@Column
private Boolean activated;
+ @Column(length = 6)
+ private String activationCode;
@Column
private LocalDate geburtsdatum;
@@ -50,6 +53,7 @@ public class RegistrationEntity {
entity.setRegistrationId(UUID.randomUUID());
entity.setEmail(registration.getEmail());
entity.setActivated(Boolean.FALSE);
+ entity.setActivationCode(String.format("%06d", new SecureRandom().nextInt(1_000_000)));
entity.setName(registration.getName());
entity.setPassword(registration.getPassword());
entity.setGeburtsdatum(registration.getGeburtsdatum());
diff --git a/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationRepository.java
index 5aa763d..90e24ac 100644
--- a/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationRepository.java
+++ b/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationRepository.java
@@ -9,4 +9,5 @@ public interface RegistrationRepository extends JpaRepository findByEmail(String email);
Optional findByName(String name);
+ Optional findByActivationCode(String activationCode);
}
diff --git a/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationService.java b/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationService.java
index 9fa20e5..6136799 100644
--- a/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationService.java
+++ b/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationService.java
@@ -32,17 +32,18 @@ public class RegistrationService {
* @throws IllegalArgumentException wenn UUID ungültig oder Registration nicht gefunden
* @throws IllegalStateException wenn Registration bereits aktiviert
*/
- public String activate(String uuid) {
- UUID registrationId;
+ public String activate(String token) {
+ RegistrationEntity registration;
try {
- registrationId = UUID.fromString(uuid);
+ UUID registrationId = UUID.fromString(token);
+ registration = registrationRepository.findById(registrationId)
+ .orElseThrow(() -> new IllegalArgumentException("Registration nicht gefunden: " + token));
} catch (IllegalArgumentException e) {
- throw new IllegalArgumentException("Ungültige UUID: " + uuid);
+ // Kein UUID-Format → nach kurzem Aktivierungscode suchen
+ registration = registrationRepository.findByActivationCode(token)
+ .orElseThrow(() -> new IllegalArgumentException("Aktivierungscode ungültig: " + token));
}
- RegistrationEntity registration = registrationRepository.findById(registrationId)
- .orElseThrow(() -> new IllegalArgumentException("Registration nicht gefunden: " + uuid));
-
if (Boolean.TRUE.equals(registration.getActivated())) {
throw new IllegalStateException("Registration bereits aktiviert");
}
@@ -52,7 +53,7 @@ public class RegistrationService {
registration.setActivated(Boolean.TRUE);
registrationRepository.save(registration);
- LOGGER.info("Registration {} aktiviert, User {} angelegt", uuid, registration.getEmail());
+ LOGGER.info("Registration {} aktiviert, User {} angelegt", token, registration.getEmail());
return registration.getEmail();
}
}
diff --git a/xxxthegame/src/main/java/de/oaa/xxx/social/SseService.java b/xxxthegame/src/main/java/de/oaa/xxx/social/SseService.java
index e58ba13..36a3f6b 100644
--- a/xxxthegame/src/main/java/de/oaa/xxx/social/SseService.java
+++ b/xxxthegame/src/main/java/de/oaa/xxx/social/SseService.java
@@ -24,7 +24,7 @@ public class SseService {
}
public SseEmitter subscribe(UUID userId) {
- SseEmitter emitter = new SseEmitter(30_000L); // 30 s – Client reconnects automatically
+ SseEmitter emitter = new SseEmitter(300_000L); // 5 min – Client reconnects automatically
emitters.computeIfAbsent(userId, k -> new CopyOnWriteArrayList<>()).add(emitter);
Runnable cleanup = () -> removeEmitter(userId, emitter);
emitter.onCompletion(cleanup);
diff --git a/xxxthegame/src/main/resources/application.properties b/xxxthegame/src/main/resources/application.properties
index 760d369..3a16508 100644
--- a/xxxthegame/src/main/resources/application.properties
+++ b/xxxthegame/src/main/resources/application.properties
@@ -1,7 +1,7 @@
# Datasource
-spring.datasource.url=jdbc:mysql://localhost:3306/xxxthegame?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
+spring.datasource.url=jdbc:mysql://localhost:3306/xxx_sphere?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
spring.datasource.username=${DB_USER:xxx}
-spring.datasource.password=${DB_PASSWORD:xxxthegame$123!}
+spring.datasource.password=${DB_PASSWORD:xxx}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JPA / Hibernate
diff --git a/xxxthegame/src/main/resources/static/activate.html b/xxxthegame/src/main/resources/static/activate.html
index 62b994d..86b7b9d 100644
--- a/xxxthegame/src/main/resources/static/activate.html
+++ b/xxxthegame/src/main/resources/static/activate.html
@@ -1,7 +1,7 @@
-
+
XXX The Game – Aktivierung
@@ -19,7 +19,7 @@
Aktivierungscode
-
+
Jetzt aktivieren
diff --git a/xxxthegame/src/main/resources/static/admin.html b/xxxthegame/src/main/resources/static/admin.html
new file mode 100644
index 0000000..90393ae
--- /dev/null
+++ b/xxxthegame/src/main/resources/static/admin.html
@@ -0,0 +1,1642 @@
+
+
+
+
+
+
+ Administration – XXX The Game
+
+
+
+
+
+
+
+
+
+
Neues System-Toy
+
Name *
+
+
Beschreibung
+
+
Bild (optional)
+
+
+
Aktuelles Bild – neues Bild wählen zum Ersetzen
+
+
+
+
+ Abbrechen
+ Speichern
+
+
+
+
+
+
+
+
Neue System-Gruppe
+
Name *
+
+
Quelle (z.B. System)
+
+
Beschreibung
+
+
Bild (optional)
+
+
+
Aktuelles Bild – neues wählen zum Ersetzen
+
+
+
+
+ Abbrechen
+ Speichern
+
+
+
+
+
+
+
+
Toy-Bildimport
+
+ Wähle mehrere Bilder aus. Für jedes Bild wird ein System-Toy angelegt –
+ Name und Beschreibung werden aus dem Dateinamen abgeleitet.
+ Die Namen können vor dem Import angepasst werden.
+
+
+
+
+
+
+ Abbrechen
+ Importieren
+
+
+
+
+
+
+
+
Aufgabe hinzufügen
+
Kurzbezeichnung *
+
+
+ Beschreibung *
+ i
+
+
+
+ In Texten können Platzhalter verwendet werden:
+ {AKTIV} – Name des aktiven Parts
+ {PASSIV} – Name des passiven Parts
+
+
+
+
+
+
+
+
Level *
+
+
Dauer (Sekunden)
+
+
+
+
+
+
+
+
+
+
+
+ Text bei Aufhebung
+
+
+
+
Benötigte Toys (optional)
+
+
+ Toy hinzufügen
+
+
+
+
Keine Toys gefunden.
+
+
+
+ Abbrechen
+ Speichern
+
+
+
+
+
+
+
+
+ Meldungen
+ Aufgabengruppen
+ Toys
+ Admins
+
+
+
+
+
+ Status:
+
+ Alle
+ Offen
+ Bearbeitet
+ Abgelehnt
+
+
+
+
+
+
+ Melder Typ Ziel-ID
+ Grund Gemeldet Status
+
+
+
+ Wird geladen…
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Benutzername User-ID Rolle Seit
+
+
+ Wird geladen…
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xxxthegame/src/main/resources/static/benutzer.html b/xxxthegame/src/main/resources/static/benutzer.html
index 168898b..d37d411 100644
--- a/xxxthegame/src/main/resources/static/benutzer.html
+++ b/xxxthegame/src/main/resources/static/benutzer.html
@@ -464,6 +464,7 @@
+
-
+
+