diff --git a/.metadata/.lock_info b/.metadata/.lock_info index bdf34f0..58ef72a 100644 --- a/.metadata/.lock_info +++ b/.metadata/.lock_info @@ -1,5 +1,5 @@ -#Sun Mar 29 16:28:09 CEST 2026 +#Mon Mar 30 07:33:01 CEST 2026 display=\:0 host=mario-mint -process-id=3723 +process-id=2955 user=mario diff --git a/.metadata/.log b/.metadata/.log index e4ad0f9..83d2901 100644 --- a/.metadata/.log +++ b/.metadata/.log @@ -1335,3 +1335,47 @@ java.io.IOException: Stream closed !ENTRY org.springframework.tooling.boot.ls 1 0 2026-03-29 22:58:21.587 !MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS +!SESSION 2026-03-30 07:32:31.775 ----------------------------------------------- +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-30 07:32:34.763 +!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized. + +!ENTRY ch.qos.logback.classic 1 0 2026-03-30 07:33:02.086 +!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-30 07:33:02.235 +!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-30 07:33:02.235 +!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-30 07:33:02.395 +!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-30 07:33:02.395 +!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-30 22:51:45.208 +!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation. +!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-30 22:51:45.208 +!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/.safetable/org.eclipse.core.resources b/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources index 27851be..62cdb19 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 cb699f1..fcc3222 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 - + @@ -11,9 +11,9 @@ topLevel shellMaximized - - - + + + persp.actionSet:org.eclipse.mylyn.tasks.ui.navigation persp.actionSet:org.eclipse.ui.cheatsheets.actionSet @@ -84,133 +84,141 @@ persp.editorOnboardingCommand:Show Key Assist$$$Shift+Ctrl+L persp.editorOnboardingCommand:New$$$Ctrl+N persp.editorOnboardingCommand:Open Type$$$Shift+Ctrl+T - - - - + + + + org.eclipse.e4.primaryNavigationStack - + View categoryTag:Java - + View categoryTag:Java - + View categoryTag:General - + View categoryTag:Java - + Minimized - + View categoryTag:Spring - - + + View categoryTag:Git - - - - + + + + org.eclipse.e4.secondaryNavigationStack - + Minimized + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Mylyn - + View categoryTag:Java - + View categoryTag:Ant - + org.eclipse.e4.secondaryDataStack Oomph Gradle Debug Version Control (Team) - + active + noFocus + 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:Debug - + View categoryTag:Version Control (Team) + + View + categoryTag:Oomph + NoRestore + - + persp.actionSet:org.eclipse.mylyn.tasks.ui.navigation persp.actionSet:org.eclipse.ui.cheatsheets.actionSet @@ -259,99 +267,99 @@ persp.editorOnboardingCommand:Step Over$$$F6 persp.editorOnboardingCommand:Step Return$$$F7 persp.editorOnboardingCommand:Resume$$$F8 - - + + org.eclipse.e4.primaryNavigationStack - + View categoryTag:Debug - + View categoryTag:General - + View categoryTag:Java - + View categoryTag:Java - + View categoryTag:Java - - - - + + + + org.eclipse.e4.secondaryNavigationStack - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Ant - - + + View categoryTag:General - + View categoryTag:General - + View categoryTag:Debug - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Terminal - + View categoryTag:Debug - + View categoryTag:General @@ -360,2719 +368,2718 @@ - - + + View categoryTag:Help - + View categoryTag:General - + View categoryTag:Help - + View categoryTag:Help - + View categoryTag:General - + ViewMenu menuContribution:menu - + - + View categoryTag:Help - - + + EditorStack - active - - + + + Editor + removeOnHide + org.eclipse.jdt.ui.CompilationUnitEditor + + + + Editor + removeOnHide + org.eclipse.jdt.ui.CompilationUnitEditor + + + + Editor + removeOnHide + org.eclipse.jdt.ui.CompilationUnitEditor + + + + Editor + removeOnHide + org.eclipse.jdt.ui.CompilationUnitEditor + + + + Editor + removeOnHide + org.eclipse.jdt.ui.CompilationUnitEditor + + + + Editor + removeOnHide + org.eclipse.jdt.ui.CompilationUnitEditor + + + Editor removeOnHide org.eclipse.ui.genericeditor.GenericEditor - - + + Editor removeOnHide org.eclipse.ui.genericeditor.GenericEditor - - + + Editor removeOnHide - org.eclipse.ui.genericeditor.GenericEditor - - - - Editor - removeOnHide - org.eclipse.ui.genericeditor.GenericEditor - - - - Editor - removeOnHide - org.eclipse.ui.genericeditor.GenericEditor - - - - Editor - removeOnHide - org.eclipse.ui.genericeditor.GenericEditor - - - - Editor - removeOnHide - org.eclipse.ui.genericeditor.GenericEditor - - - - Editor - removeOnHide - org.eclipse.ui.genericeditor.GenericEditor - - - - Editor - removeOnHide - org.eclipse.ui.genericeditor.GenericEditor - - - - Editor - removeOnHide - org.eclipse.ui.genericeditor.GenericEditor - - - - Editor - removeOnHide - org.eclipse.ui.genericeditor.GenericEditor - - - - Editor - removeOnHide - org.eclipse.ui.genericeditor.GenericEditor - active - activeOnClose + org.eclipse.jdt.ui.CompilationUnitEditor - + View categoryTag:Java - + ViewMenu menuContribution:menu - + - + View categoryTag:Java - + View categoryTag:General - + - + View categoryTag:General - + ViewMenu menuContribution:menu - + - + View categoryTag:Java - + View categoryTag:Java - + - + View categoryTag:General - + ViewMenu menuContribution:menu - + - + View categoryTag:General - + active + ViewMenu menuContribution:menu - + - + View categoryTag:General - + View categoryTag:General - + ViewMenu menuContribution:menu - + - + 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:Debug - + ViewMenu menuContribution:menu - + - + View categoryTag:Debug - + View categoryTag:Debug - + ViewMenu menuContribution:menu - + - + View categoryTag:Debug - + ViewMenu menuContribution:menu - + - + View categoryTag:Debug - + ViewMenu menuContribution:menu - + - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Debug - + ViewMenu menuContribution:menu - + - + View categoryTag:Version Control (Team) - + 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 - + TrimStack Draggable - + TrimStack Draggable - + TrimStack Draggable - - + + TrimStack Draggable - + TrimStack Draggable - + TrimStack Draggable - + TrimStack 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.m2e.logback/0.log b/.metadata/.plugins/org.eclipse.m2e.logback/0.log index aab21ed..6380965 100644 --- a/.metadata/.plugins/org.eclipse.m2e.logback/0.log +++ b/.metadata/.plugins/org.eclipse.m2e.logback/0.log @@ -15,3 +15,4 @@ 2026-03-26 16:50:11,098 [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-27 07:46:24,300 [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-29 16:28:13,219 [Worker-2: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is out-of-date. Trying to update. +2026-03-30 07:33:05,316 [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. diff --git a/.metadata/version.ini b/.metadata/version.ini index 8584934..58386a1 100644 --- a/.metadata/version.ini +++ b/.metadata/version.ini @@ -1,3 +1,3 @@ -#Sun Mar 29 16:28:09 CEST 2026 +#Mon Mar 30 07:33:01 CEST 2026 org.eclipse.core.runtime=2 org.eclipse.platform=4.39.0.v20260226-0420 diff --git a/bilder/toys/bodenpranger.png b/bilder/toys/bodenpranger.png new file mode 100644 index 0000000..a43da28 Binary files /dev/null and b/bilder/toys/bodenpranger.png differ diff --git a/bilder/toys/prangerhandfuss.png b/bilder/toys/prangerhandfuss.png new file mode 100644 index 0000000..214d8c9 Binary files /dev/null and b/bilder/toys/prangerhandfuss.png differ diff --git a/bilder/toys/prangerhandhals.png b/bilder/toys/prangerhandhals.png new file mode 100644 index 0000000..968d973 Binary files /dev/null and b/bilder/toys/prangerhandhals.png differ diff --git a/bilder/toys/raw/41-54Dp0rLL._AC_SY450_.jpg b/bilder/toys/raw/41-54Dp0rLL._AC_SY450_.jpg deleted file mode 100644 index dc8f1f9..0000000 Binary files a/bilder/toys/raw/41-54Dp0rLL._AC_SY450_.jpg and /dev/null differ diff --git a/bilder/toys/raw/41FbgSjf5kL._AC_SL1000_.jpg b/bilder/toys/raw/41FbgSjf5kL._AC_SL1000_.jpg new file mode 100644 index 0000000..cb1f1b1 Binary files /dev/null and b/bilder/toys/raw/41FbgSjf5kL._AC_SL1000_.jpg differ diff --git a/bilder/toys/raw/il_1140xN.6566343643_8mh3.webp b/bilder/toys/raw/il_1140xN.6566343643_8mh3.webp new file mode 100644 index 0000000..53fcd42 Binary files /dev/null and b/bilder/toys/raw/il_1140xN.6566343643_8mh3.webp differ diff --git a/bilder/toys/raw/il_794xN.6730546998_e67q.webp b/bilder/toys/raw/il_794xN.6730546998_e67q.webp new file mode 100644 index 0000000..4025594 Binary files /dev/null and b/bilder/toys/raw/il_794xN.6730546998_e67q.webp differ diff --git a/testdaten/aufgabengruppen-export.zip b/testdaten/aufgabengruppen-export.zip index ca52c80..03cec53 100644 Binary files a/testdaten/aufgabengruppen-export.zip and b/testdaten/aufgabengruppen-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 index 4ded650..4ab4401 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/admin/AdminController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/admin/AdminController.java @@ -254,6 +254,40 @@ public class AdminController { return ResponseEntity.noContent().build(); } + // ── Item verschieben ───────────────────────────────────────────────────── + + @PutMapping("/aufgabengruppen/items/{kind}/{itemId}/move") + public ResponseEntity moveItem( + @PathVariable("kind") String kind, + @PathVariable("itemId") UUID itemId, + @RequestParam("targetGruppeId") UUID targetGruppeId, + Principal principal) { + requireAdmin(principal); + AufgabenGruppeEntity targetGruppe = aufgabenGruppeRepository.findById(targetGruppeId) + .orElseThrow(() -> new org.springframework.web.server.ResponseStatusException( + org.springframework.http.HttpStatus.NOT_FOUND, "Zielgruppe nicht gefunden")); + switch (kind) { + case "aufgabe" -> aufgabeRepository.findById(itemId).ifPresent(e -> { + e.setAufgabenGruppe(targetGruppe); + aufgabeRepository.save(e); + }); + case "strafe" -> strafeRepository.findById(itemId).ifPresent(e -> { + e.setAufgabenGruppe(targetGruppe); + strafeRepository.save(e); + }); + case "zeitstrafe" -> sperreRepository.findById(itemId).ifPresent(e -> { + e.setAufgabenGruppe(targetGruppe); + sperreRepository.save(e); + }); + case "finisher" -> finisherRepository.findById(itemId).ifPresent(e -> { + e.setAufgabenGruppe(targetGruppe); + finisherRepository.save(e); + }); + default -> { return ResponseEntity.badRequest().build(); } + } + return ResponseEntity.noContent().build(); + } + // ── Toys ───────────────────────────────────────────────────────────────── @GetMapping("/toys") 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 a897c64..e73b6cf 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/config/SecurityConfig.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/config/SecurityConfig.java @@ -49,12 +49,10 @@ public class SecurityConfig { .requestMatchers("/games/chastity/sessionchastity.html").authenticated() .requestMatchers("/games/chastity/neulock.html").authenticated() .requestMatchers("/games/chastity/activelock.html").authenticated() - .requestMatchers("/sessionbdsmtasks.html").authenticated() .requestMatchers("/sessionbdsmtoys.html").authenticated() .requestMatchers("/sessionbdsmingame.html").authenticated() .requestMatchers("/games/bdsm/neubdsm.html").authenticated() .requestMatchers("/games/bdsm/bdsmingame.html").authenticated() - .requestMatchers("/games/bdsm/bdsmwarten.html").authenticated() .requestMatchers("/community/personen-suchen.html").authenticated() .requestMatchers("/community/freunde.html").authenticated() .requestMatchers("/community/nachrichten.html").authenticated() @@ -69,7 +67,7 @@ public class SecurityConfig { .requestMatchers("/games/chastity/meine-locks.html").authenticated() .requestMatchers("/games/chastity/entdecken-vorlagen.html").authenticated() .requestMatchers("/games/chastity/unlock-history.html").authenticated() - .requestMatchers("/community/einladungen.html").authenticated() + .requestMatchers("/games/common/einladungen.html").authenticated() .requestMatchers("/games/chastity/joinlock.html").authenticated() .requestMatchers("/community/benachrichtigungen.html").authenticated() .requestMatchers("/community/abonnements.html").authenticated() diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/controller/BdsmEinladungController.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/controller/BdsmEinladungController.java index 339f9b9..2ec9d67 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/controller/BdsmEinladungController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/controller/BdsmEinladungController.java @@ -23,7 +23,6 @@ import de.oaa.xxx.games.bdsm.entity.BdsmEinladungEntity; import de.oaa.xxx.games.bdsm.entity.BdsmEinladungEntity.Status; import de.oaa.xxx.games.bdsm.repository.BdsmEinladungRepository; import de.oaa.xxx.social.SystemMessageService; -import de.oaa.xxx.social.entity.MessageCause; import de.oaa.xxx.social.repository.FriendshipRepository; import de.oaa.xxx.user.UserRepository; import de.oaa.xxx.user.UserService; @@ -97,13 +96,7 @@ public class BdsmEinladungController { entity.setCreatedAt(LocalDateTime.now()); einladungRepository.save(entity); - String inviterName = userRepository.findById(inviterId).map(u -> u.getName()).orElse("Jemand"); - systemMessageService.send( - inviterId, req.inviteeId(), - inviterName + " hat dich zum BDSM Game eingeladen.", - "/community/einladungen.html", - MessageCause.INVITATION - ); + systemMessageService.pushInvitationUpdate(req.inviteeId()); Map result = new LinkedHashMap<>(); result.put("einladungId", entity.getEinladungId()); @@ -118,10 +111,7 @@ public class BdsmEinladungController { if (e == null) return ResponseEntity.notFound().build(); if (!e.getInviterId().equals(userId)) return ResponseEntity.status(403).build(); e.setStatus(Status.CANCELLED); - String inviterName = userRepository.findById(userId).map(u -> u.getName()).orElse("Jemand"); - systemMessageService.send(userId, e.getInviteeId(), - inviterName + " hat die BDSM-Spieleinladung zurückgezogen.", - "/community/einladungen.html", MessageCause.INVITATION); + systemMessageService.pushInvitationUpdate(e.getInviteeId()); return ResponseEntity.accepted().build(); } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockController.java b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockController.java index 3c20039..a172748 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockController.java @@ -189,10 +189,7 @@ public class CardLockController { inv.setDetailsVisible(req.lockeeDetailsVisible()); lockeeInvitationRepository.save(inv); - String lockName = req.name() != null && !req.name().isBlank() ? req.name() : "Unbenanntes Lock"; - sendMessage(myId, lockee.getUserId(), - me.getName() + " hat dich als Lockee für das Lock „" + lockName + "\" eingeladen.", - "/community/einladungen.html", de.oaa.xxx.social.entity.MessageCause.INVITATION); + systemMessageService.pushInvitationUpdate(lockee.getUserId()); return ResponseEntity.ok(Map.of("lockId", lock.getLockId().toString(), "lockeeInvitationSent", true)); } @@ -261,10 +258,7 @@ public class CardLockController { inv.setCreatedAt(now); invitationRepository.save(inv); - String lockName = req.name() != null && !req.name().isBlank() ? req.name() : "Unbenanntes Lock"; - sendMessage(me.getUserId(), kh.getUserId(), - me.getName() + " hat dich als Keyholder*In für das Lock „" + lockName + "\" eingeladen.", - "/community/einladungen.html", de.oaa.xxx.social.entity.MessageCause.INVITATION); + systemMessageService.pushInvitationUpdate(kh.getUserId()); keyholderPending = true; } @@ -772,10 +766,7 @@ public class CardLockController { if (lockOpt.isPresent()) { var lock = lockOpt.get(); - String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock"; - sendMessage(myId, lock.getLockee(), - me.getName() + " hat die Einladung als Keyholder*In für das Lock „" + lockName + "\" abgelehnt.", - null, de.oaa.xxx.social.entity.MessageCause.INVITATION); + systemMessageService.pushInvitationUpdate(lock.getLockee()); } return ResponseEntity.noContent().build(); @@ -828,12 +819,7 @@ public class CardLockController { invitationRepository.delete(inv); - String lockName = lockOpt.get().getName() != null && !lockOpt.get().getName().isBlank() - ? lockOpt.get().getName() - : "Unbenanntes Lock"; - sendMessage(myId, inv.getKeyholderUserId(), - me.getName() + " hat die Keyholder-Einladung für das Lock „" + lockName + "\" zurückgezogen.", null, - de.oaa.xxx.social.entity.MessageCause.INVITATION); + systemMessageService.pushInvitationUpdate(inv.getKeyholderUserId()); return ResponseEntity.noContent().build(); } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/keyholder/KeyholderOfferController.java b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/keyholder/KeyholderOfferController.java index 32e599e..c308ebb 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/keyholder/KeyholderOfferController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/keyholder/KeyholderOfferController.java @@ -37,7 +37,6 @@ import de.oaa.xxx.games.chastity.timelock.TimeLockRepository; import de.oaa.xxx.games.chastity.timelock.TimeLockServiceFactory; import de.oaa.xxx.games.chastity.timelock.TimeLockTemplateEntity; import de.oaa.xxx.social.SystemMessageService; -import de.oaa.xxx.social.entity.MessageCause; import de.oaa.xxx.subscription.SubscriptionLimitService; import de.oaa.xxx.user.UserRepository; import de.oaa.xxx.user.UserService; @@ -319,7 +318,6 @@ public class KeyholderOfferController { return ResponseEntity.status(500).build(); } - String lockName = template.getName() != null ? template.getName() : "Unbenanntes Lock"; boolean invitationSent = false; if (!directStart) { // Normaler Einladungsworkflow: Keyholder muss bestätigen @@ -331,17 +329,11 @@ public class KeyholderOfferController { inv.setCreatedAt(LocalDateTime.now()); invitationRepository.save(inv); - systemMessageService.send(myId, offerer.getUserId(), - me.getName() + " möchte dein Keyholder-Angebot annehmen und lädt dich als Keyholder für „" - + lockName + "\" ein.", - "/community/einladungen.html", MessageCause.INVITATION); + systemMessageService.pushInvitationUpdate(offerer.getUserId()); invitationSent = true; } else { - // Direktstart: Keyholder wird direkt gesetzt, aber trotzdem benachrichtigen - systemMessageService.send(myId, offerer.getUserId(), - me.getName() + " hat dein Keyholder-Angebot angenommen und das Lock „" - + lockName + "\" gestartet.", - "/games/chastity/keyholder.html", MessageCause.INVITATION); + // Direktstart: Keyholder wird direkt gesetzt + systemMessageService.pushInvitationUpdate(offerer.getUserId()); } // Annahmezähler erhöhen diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/lockee/LockeeInvitationController.java b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/lockee/LockeeInvitationController.java index 68d0c4e..a17b212 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/lockee/LockeeInvitationController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/lockee/LockeeInvitationController.java @@ -65,10 +65,6 @@ public class LockeeInvitationController { this.userService = userService; } - private void sendMessage(UUID senderId, UUID receiverId, String text, String targetUrl, de.oaa.xxx.social.entity.MessageCause cause) { - systemMessageService.send(senderId, receiverId, text, targetUrl, cause); - } - private String generateUnlockCode(int lines) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < lines; i++) sb.append(RNG.nextInt(10)); @@ -165,10 +161,7 @@ public class LockeeInvitationController { if (lockOpt.isPresent()) { var lock = lockOpt.get(); baseLockRepository.delete(lock); - String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock"; - sendMessage(myId, inv.getLockeeUserId(), - me.getName() + " hat die Lockee-Einladung für das Lock „" + lockName + "\" zurückgezogen.", - null, de.oaa.xxx.social.entity.MessageCause.INVITATION); + systemMessageService.pushInvitationUpdate(inv.getLockeeUserId()); } return ResponseEntity.noContent().build(); @@ -245,7 +238,6 @@ public class LockeeInvitationController { LocalDateTime now = LocalDateTime.now(); String unlockCode; - String lockName; if (lock instanceof CardLockEntity cardLock) { unlockCode = generateUnlockCode(codeLines); @@ -259,7 +251,6 @@ public class LockeeInvitationController { cardLock.setLastHygineOpening(now); } cardlockRepository.save(cardLock); - lockName = cardLock.getName() != null && !cardLock.getName().isBlank() ? cardLock.getName() : "Unbenanntes Lock"; } else if (lock instanceof TimeLockEntity timeLock) { unlockCode = CodeCreator.createNumeric(codeLines); int unlockMinutes = randomBetween(timeLock.getMinTimeInMinutes(), timeLock.getMaxTimeInMinutes()); @@ -271,16 +262,13 @@ public class LockeeInvitationController { timeLock.setLastHygineOpening(now); } timeLockRepository.save(timeLock); - lockName = timeLock.getName() != null && !timeLock.getName().isBlank() ? timeLock.getName() : "Unbenanntes Lock"; } else { return ResponseEntity.status(500).build(); } lockeeInvitationRepository.delete(inv); - sendMessage(myId, inv.getKeyholderUserId(), - me.getName() + " hat die Einladung als Lockee für das Lock „" + lockName + "\" angenommen.", - "/games/chastity/keyholder.html", de.oaa.xxx.social.entity.MessageCause.INVITATION); + systemMessageService.pushInvitationUpdate(inv.getKeyholderUserId()); return ResponseEntity.ok(Map.of( "lockId", lock.getLockId().toString(), @@ -305,10 +293,7 @@ public class LockeeInvitationController { if (lockOpt.isPresent()) { var lock = lockOpt.get(); baseLockRepository.delete(lock); - String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock"; - sendMessage(myId, inv.getKeyholderUserId(), - me.getName() + " hat die Einladung als Lockee für das Lock „" + lockName + "\" abgelehnt.", - null, de.oaa.xxx.social.entity.MessageCause.INVITATION); + systemMessageService.pushInvitationUpdate(inv.getKeyholderUserId()); } return ResponseEntity.noContent().build(); diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/timelock/TimeLockController.java b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/timelock/TimeLockController.java index b5b7259..f45df26 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/timelock/TimeLockController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/timelock/TimeLockController.java @@ -140,10 +140,7 @@ public class TimeLockController { inv.setDetailsVisible(req.lockeeDetailsVisible()); lockeeInvitationRepository.save(inv); - String lockName = template.getName() != null ? template.getName() : "Unbenanntes Lock"; - systemMessageService.send(myId, lockee.getUserId(), - me.getName() + " hat dich als Lockee für das Lock „" + lockName + "\" eingeladen.", - "/community/einladungen.html", de.oaa.xxx.social.entity.MessageCause.INVITATION); + systemMessageService.pushInvitationUpdate(lockee.getUserId()); return ResponseEntity.ok(Map.of( "lockId", lock.getLockId().toString(), @@ -178,10 +175,7 @@ public class TimeLockController { inv.setCreatedAt(LocalDateTime.now()); invitationRepository.save(inv); - String lockName = template.getName() != null ? template.getName() : "Unbenanntes Lock"; - systemMessageService.send(myId, kh.getUserId(), - me.getName() + " hat dich als Keyholder*In für das Lock „" + lockName + "\" eingeladen.", - "/community/einladungen.html", de.oaa.xxx.social.entity.MessageCause.INVITATION); + systemMessageService.pushInvitationUpdate(kh.getUserId()); keyholderPending = true; } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/common/repository/AufgabenGruppeRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/games/common/repository/AufgabenGruppeRepository.java index e997610..db813fe 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/common/repository/AufgabenGruppeRepository.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/common/repository/AufgabenGruppeRepository.java @@ -3,6 +3,7 @@ package de.oaa.xxx.games.common.repository; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -37,4 +38,10 @@ public interface AufgabenGruppeRepository extends JpaRepository :userId AND g.strafen IS EMPTY AND g.sperren IS EMPTY AND (:name IS NULL OR LOWER(g.name) LIKE LOWER(:name))") List findVanillaSafePublicFromOthers(@Param("userId") UUID userId, @Param("name") String name); + + @Query("SELECT g FROM AufgabenGruppeEntity g WHERE g.userId = :userId AND (g.aufgaben IS NOT EMPTY OR g.finisher IS NOT EMPTY)") + Page findByUserIdWithContent(@Param("userId") UUID userId, Pageable pageable); + + @Query("SELECT g FROM AufgabenGruppeEntity g WHERE g.userId IS NULL AND (g.aufgaben IS NOT EMPTY OR g.finisher IS NOT EMPTY)") + Page findSystemGroupsWithContent(Pageable pageable); } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/common/repository/GruppenAboRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/games/common/repository/GruppenAboRepository.java index be98ed4..81a9dd7 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/common/repository/GruppenAboRepository.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/common/repository/GruppenAboRepository.java @@ -1,6 +1,10 @@ package de.oaa.xxx.games.common.repository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import de.oaa.xxx.games.common.entity.AufgabenGruppeEntity; import de.oaa.xxx.games.common.entity.GruppenAboEntity; @@ -19,4 +23,8 @@ public interface GruppenAboRepository extends JpaRepository findByUserIdWithContent(@Param("userId") UUID userId, Pageable pageable); } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaAboController.java b/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaAboController.java index ccd6672..dd99b0d 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaAboController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaAboController.java @@ -1,13 +1,14 @@ package de.oaa.xxx.games.vanilla.controller; -import de.oaa.xxx.games.common.aufgaben.AufgabenGruppe; -import de.oaa.xxx.games.common.aufgaben.AufgabenGruppePage; -import de.oaa.xxx.games.common.entity.AufgabenGruppeEntity; -import de.oaa.xxx.games.common.entity.GruppenAboEntity; -import de.oaa.xxx.games.common.repository.AufgabenGruppeRepository; -import de.oaa.xxx.games.common.repository.GruppenAboRepository; -import de.oaa.xxx.user.UserEntity; -import de.oaa.xxx.user.UserService; +import java.security.Principal; +import java.util.List; +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +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.DeleteMapping; @@ -18,13 +19,14 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.security.Principal; -import java.util.Comparator; -import java.util.List; -import java.util.UUID; +import de.oaa.xxx.games.common.aufgaben.AufgabenGruppe; +import de.oaa.xxx.games.common.aufgaben.AufgabenGruppePage; +import de.oaa.xxx.games.common.entity.AufgabenGruppeEntity; +import de.oaa.xxx.games.common.entity.GruppenAboEntity; +import de.oaa.xxx.games.common.repository.AufgabenGruppeRepository; +import de.oaa.xxx.games.common.repository.GruppenAboRepository; +import de.oaa.xxx.user.UserEntity; +import de.oaa.xxx.user.UserService; @RestController @RequestMapping("/vanilla/abo") @@ -56,15 +58,17 @@ public class VanillaAboController { Principal principal) { UserEntity user = userService.requireUser(principal); - List dtos = aboRepository.findByUserId(user.getUserId()).stream() - .map(GruppenAboEntity::getAufgabenGruppe) - .filter(g -> !g.isPrivateGruppe()) - .filter(g -> g.getStrafen().isEmpty() && g.getSperren().isEmpty()) - .map(g -> enrich(g, user.getUserId(), true)) - .sorted(Comparator.comparing(AufgabenGruppe::getName, String.CASE_INSENSITIVE_ORDER)) + Page dbPage = aboRepository.findByUserIdWithContent( + user.getUserId(), PageRequest.of(page, size, Sort.by("aufgabenGruppe.name"))); + List dtos = dbPage.getContent().stream() + .map(a -> enrich(a.getAufgabenGruppe(), user.getUserId(), true)) .toList(); - - return ResponseEntity.ok(manualPage(dtos, page, size)); + AufgabenGruppePage result = new AufgabenGruppePage(); + result.setContent(dtos); + result.setCurrentPage(dbPage.getNumber()); + result.setTotalPages(dbPage.getTotalPages()); + result.setTotalElements(dbPage.getTotalElements()); + return ResponseEntity.ok(result); } // ── Entdecken (nur vanilla-safe Gruppen von anderen) ── @@ -82,11 +86,19 @@ public class VanillaAboController { List dtos = gruppeRepository .findVanillaSafePublicFromOthers(user.getUserId(), namePattern).stream() .map(g -> enrich(g, user.getUserId(), aboRepository.existsByUserIdAndAufgabenGruppe(user.getUserId(), g))) - .sorted(Comparator.comparingLong(AufgabenGruppe::getSubscriberCount).reversed() + .sorted(java.util.Comparator.comparingLong(AufgabenGruppe::getSubscriberCount).reversed() .thenComparing(AufgabenGruppe::getName, String.CASE_INSENSITIVE_ORDER)) .toList(); - return ResponseEntity.ok(manualPage(dtos, page, size)); + int total = dtos.size(); + int start = page * size; + List content = start >= total ? List.of() : dtos.subList(start, Math.min(start + size, total)); + AufgabenGruppePage discoverPage = new AufgabenGruppePage(); + discoverPage.setContent(content); + discoverPage.setCurrentPage(page); + discoverPage.setTotalPages(total == 0 ? 1 : (int) Math.ceil((double) total / size)); + discoverPage.setTotalElements(total); + return ResponseEntity.ok(discoverPage); } // ── Abonnieren (nur vanilla-safe) ── @@ -138,16 +150,5 @@ public class VanillaAboController { return g; } - private AufgabenGruppePage manualPage(List all, int page, int size) { - int total = all.size(); - int start = page * size; - List content = start >= total ? List.of() : all.subList(start, Math.min(start + size, total)); - AufgabenGruppePage result = new AufgabenGruppePage(); - result.setContent(content); - result.setCurrentPage(page); - result.setTotalPages(total == 0 ? 1 : (int) Math.ceil((double) total / size)); - result.setTotalElements(total); - return result; - } } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaAufgabenGruppeController.java b/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaAufgabenGruppeController.java index 87a87f4..8c988be 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaAufgabenGruppeController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaAufgabenGruppeController.java @@ -84,18 +84,17 @@ public class VanillaAufgabenGruppeController { Principal principal) { UserEntity user = resolveUser(principal); if (user == null) return ResponseEntity.status(401).build(); - // Only vanilla-safe user groups (no Strafen, no Sperren) - UUID userId = user.getUserId(); - String searchPattern = null; - java.util.List all = gruppeRepository.listVanillaSafeWithUserAndSearch( - userId, searchPattern, PageRequest.of(0, 500, Sort.by("name"))); - java.util.List ownOnly = all.stream() - .filter(g -> userId.equals(g.getUserId())).toList(); - AufgabenGruppePage result = manualPage(ownOnly.stream().map(entity -> { + Page dbPage = gruppeRepository.findByUserIdWithContent( + user.getUserId(), PageRequest.of(page, size, Sort.by("name"))); + AufgabenGruppePage result = new AufgabenGruppePage(); + result.setContent(dbPage.getContent().stream().map(entity -> { AufgabenGruppe g = entity.toAufgabenGruppe(); g.setSubscriberCount(aboRepository.countByAufgabenGruppe(entity)); return g; - }).toList(), page, DEFAULT_PAGE_SIZE); + }).toList()); + result.setCurrentPage(dbPage.getNumber()); + result.setTotalPages(dbPage.getTotalPages()); + result.setTotalElements(dbPage.getTotalElements()); return ResponseEntity.ok(result); } @@ -103,16 +102,14 @@ public class VanillaAufgabenGruppeController { public ResponseEntity listSystem( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "" + DEFAULT_PAGE_SIZE) int size) { - // Only vanilla-safe system groups (userId IS NULL → no strafen/sperren anyway, but filter for safety) - Page result = gruppeRepository.findByUserIdIsNull( + Page dbPage = gruppeRepository.findSystemGroupsWithContent( PageRequest.of(page, size, Sort.by("name"))); AufgabenGruppePage r = new AufgabenGruppePage(); - r.setContent(result.getContent().stream() - .filter(g -> g.getStrafen().isEmpty() && g.getSperren().isEmpty()) + r.setContent(dbPage.getContent().stream() .map(AufgabenGruppeEntity::toAufgabenGruppe).toList()); - r.setCurrentPage(result.getNumber()); - r.setTotalPages(result.getTotalPages()); - r.setTotalElements(result.getTotalElements()); + r.setCurrentPage(dbPage.getNumber()); + r.setTotalPages(dbPage.getTotalPages()); + r.setTotalElements(dbPage.getTotalElements()); return ResponseEntity.ok(r); } @@ -270,15 +267,4 @@ public class VanillaAufgabenGruppeController { return userService.requireUser(principal); } - private AufgabenGruppePage manualPage(java.util.List all, int page, int size) { - int total = all.size(); - int start = page * size; - java.util.List content = start >= total ? java.util.List.of() : all.subList(start, Math.min(start + size, total)); - AufgabenGruppePage result = new AufgabenGruppePage(); - result.setContent(content); - result.setCurrentPage(page); - result.setTotalPages(total == 0 ? 1 : (int) Math.ceil((double) total / size)); - result.setTotalElements(total); - return result; - } } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaEinladungController.java b/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaEinladungController.java index 3006053..8d3c035 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaEinladungController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaEinladungController.java @@ -23,7 +23,6 @@ import de.oaa.xxx.games.vanilla.entity.VanillaEinladungEntity; import de.oaa.xxx.games.vanilla.entity.VanillaEinladungEntity.Status; import de.oaa.xxx.games.vanilla.repository.VanillaEinladungRepository; import de.oaa.xxx.social.SystemMessageService; -import de.oaa.xxx.social.entity.MessageCause; import de.oaa.xxx.social.repository.FriendshipRepository; import de.oaa.xxx.user.UserRepository; import de.oaa.xxx.user.UserService; @@ -106,13 +105,7 @@ public class VanillaEinladungController { entity.setCreatedAt(LocalDateTime.now()); einladungRepository.save(entity); - String inviterName = userRepository.findById(inviterId).map(u -> u.getName()).orElse("Jemand"); - systemMessageService.send( - inviterId, req.inviteeId(), - inviterName + " hat dich zum Vanilla Game eingeladen.", - "/community/einladungen.html", - MessageCause.INVITATION - ); + systemMessageService.pushInvitationUpdate(req.inviteeId()); Map result = new LinkedHashMap<>(); result.put("einladungId", entity.getEinladungId()); @@ -127,10 +120,7 @@ public class VanillaEinladungController { if (e == null) return ResponseEntity.notFound().build(); if (!e.getInviterId().equals(userId)) return ResponseEntity.status(403).build(); e.setStatus(Status.CANCELLED); - String inviterName = userRepository.findById(userId).map(u -> u.getName()).orElse("Jemand"); - systemMessageService.send(userId, e.getInviteeId(), - inviterName + " hat die Vanilla-Spieleinladung zurückgezogen.", - "/community/einladungen.html", MessageCause.INVITATION); + systemMessageService.pushInvitationUpdate(e.getInviteeId()); return ResponseEntity.accepted().build(); } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/social/SystemMessageService.java b/xxxthegame/src/main/java/de/oaa/xxx/social/SystemMessageService.java index 3239cb6..32dfcf8 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/social/SystemMessageService.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/social/SystemMessageService.java @@ -57,8 +57,8 @@ public class SystemMessageService { .findByUserIdAndCause(receiverId, cause) .orElseGet(() -> NotificationPreferenceEntity.defaultFor(receiverId, cause)); - // FRIENDREQUEST ist immer in-app, unabhängig von der Einstellung - boolean sendInApp = cause == MessageCause.FRIENDREQUEST || pref.isInApp(); + // FRIENDREQUEST und INVITATION sind immer nur in-app, kein E-Mail + boolean sendInApp = cause == MessageCause.FRIENDREQUEST || cause == MessageCause.INVITATION || pref.isInApp(); if (sendInApp) { MessageEntity msg = new MessageEntity(); @@ -76,7 +76,7 @@ public class SystemMessageService { sseService.push(receiverId, "NOTIFICATION", Map.of("unreadCount", unread, "text", text)); } - if (pref.isEmail()) { + if (pref.isEmail() && cause != MessageCause.INVITATION) { userRepository.findById(receiverId).ifPresent(user -> { try { Email email = new Email(); @@ -91,6 +91,15 @@ public class SystemMessageService { } } + /** + * Benachrichtigt den Empfänger per SSE, dass sich seine Einladungsliste geändert hat, + * ohne eine In-App-Nachricht oder E-Mail zu erstellen. + */ + public void pushInvitationUpdate(UUID receiverId) { + if (receiverId == null) return; + sseService.push(receiverId, "INVITATION", java.util.Map.of()); + } + private String causeTitel(MessageCause cause) { return switch (cause) { case INVITATION -> "XXX The Game – Neue Einladung"; diff --git a/xxxthegame/src/main/resources/sql/testdata_aufgabengruppen.sql b/xxxthegame/src/main/resources/sql/testdata_aufgabengruppen.sql new file mode 100644 index 0000000..a87052a --- /dev/null +++ b/xxxthegame/src/main/resources/sql/testdata_aufgabengruppen.sql @@ -0,0 +1,588 @@ +-- ============================================================ +-- Testdaten: Aufgabengruppen (generiert aus DefaultFiller) +-- Toys und *Toy-Join-Tabellen werden ignoriert. +-- UUID-Speicherung: varchar(36) als plain UUID-String +-- Spaltennamen: SpringPhysicalNamingStrategy → snake_case +-- ============================================================ + +SET NAMES utf8mb4; + +-- ── Aufgabengruppen ────────────────────────────────────────── +INSERT IGNORE INTO aufgaben_gruppe (gruppen_id, name, beschreibung, user_id, private_gruppe, bild, von) VALUES +('10000000-0000-0000-0000-000000000001', 'Keuschhaltung weiblich', 'Enthält verschiedene Aufgaben für Keuschhaltung von weiblichen Spielpartnern', NULL, 0, NULL, NULL), +('10000000-0000-0000-0000-000000000002', 'Keuschhaltung männlich', 'Enthält verschiedene Aufgaben für Keuschhaltung von männlichen Spielpartnern', NULL, 0, NULL, NULL), +('10000000-0000-0000-0000-000000000003', 'Plugs', 'Enthält verschiedene Aufgaben für das Tragen von Buttplugs über einen gewissen Zeitraum.', NULL, 0, NULL, NULL), +('10000000-0000-0000-0000-000000000004', 'Knebel', 'Enthält verschiedene Aufgaben für das Tragen von Knebeln über einen gewissen Zeitraum.', NULL, 0, NULL, NULL), +('10000000-0000-0000-0000-000000000005', 'Strafen', 'Enthält verschiedene Bestrafungen', NULL, 0, NULL, NULL), +('10000000-0000-0000-0000-000000000006', 'Aufgaben', 'Enthält verschiedene Sex-Aufgaben.', NULL, 0, NULL, NULL); + + +-- ── Sperren ────────────────────────────────────────────────── +-- Gruppe: Keuschhaltung weiblich +INSERT IGNORE INTO sperre (sperre_id, kurz_text, text, release_text, minuten_von, minuten_bis, gruppe_id) VALUES +('20000000-0000-0000-0000-000000000001', 'Voll-KG', + '{PASSIV} trägt fortan einen Voll-KG, {AKTIV} ist der Keyholder', + '{AKTIV}, es ist ab der Zeit {PASSIV} von ihrem KG zu befreien', + 10, 30, '10000000-0000-0000-0000-000000000001'), + +('20000000-0000-0000-0000-000000000002', 'Voll-KG + Vaginaldildo', + '{PASSIV} trägt fortan einen Voll-KG mit Vaginaldildo, {AKTIV} ist der Keyholder', + '{AKTIV}, es ist ab der Zeit {PASSIV} von ihrem KG zu befreien', + 10, 30, '10000000-0000-0000-0000-000000000001'), + +('20000000-0000-0000-0000-000000000003', 'Voll-KG + Analdildo', + '{PASSIV} trägt fortan einen Voll-KG mit Analdildo, {AKTIV} ist der Keyholder', + '{AKTIV}, es ist ab der Zeit {PASSIV} von ihrem KG zu befreien', + 10, 30, '10000000-0000-0000-0000-000000000001'), + +('20000000-0000-0000-0000-000000000004', 'Voll-KG + Doubleplugged', + '{PASSIV} trägt fortan einen Voll-KG mit Vaginal- und Analdildo, {AKTIV} ist der Keyholder', + '{AKTIV}, es ist ab der Zeit {PASSIV} von ihrem KG zu befreien', + 10, 30, '10000000-0000-0000-0000-000000000001'); + +-- Gruppe: Keuschhaltung männlich +INSERT IGNORE INTO sperre (sperre_id, kurz_text, text, release_text, minuten_von, minuten_bis, gruppe_id) VALUES +('20000000-0000-0000-0000-000000000005', 'Peniskäfig', + '{PASSIV} trägt fortan einen Peniskäfig, {AKTIV} ist der Keyholder', + '{AKTIV}, es ist ab der Zeit {PASSIV} von seinem Peniskäfig zu befreien', + 10, 30, '10000000-0000-0000-0000-000000000002'), + +('20000000-0000-0000-0000-000000000006', 'Voll-KG', + '{PASSIV} trägt fortan einen Voll-KG, {AKTIV} ist der Keyholder', + '{AKTIV}, es ist ab der Zeit {PASSIV} von seinem KG zu befreien', + 10, 30, '10000000-0000-0000-0000-000000000002'), + +('20000000-0000-0000-0000-000000000007', 'Voll-KG + Analdildo', + '{PASSIV} trägt fortan einen Voll-KG mit Analdildo, {AKTIV} ist der Keyholder', + '{AKTIV}, es ist ab der Zeit {PASSIV} von seinem KG zu befreien', + 10, 30, '10000000-0000-0000-0000-000000000002'); + +-- Gruppe: Plugs +INSERT IGNORE INTO sperre (sperre_id, kurz_text, text, release_text, minuten_von, minuten_bis, gruppe_id) VALUES +('20000000-0000-0000-0000-000000000008', 'Plug klein', + '{AKTIV} führt {PASSIV} einen kleinen Buttplug in anal ein, dieser ist bis auf weiteres zu tragen.', + '{AKTIV}, es ist Zeit {PASSIV} von seinem Plug zu befreien', + 10, 30, '10000000-0000-0000-0000-000000000003'), + +('20000000-0000-0000-0000-000000000009', 'Plug mittel', + '{AKTIV} führt {PASSIV} einen mittelgroßen Buttplug anal ein, dieser ist bis auf weiteres zu tragen.', + '{AKTIV}, es ist Zeit {PASSIV} von seinem Plug zu befreien', + 10, 30, '10000000-0000-0000-0000-000000000003'), + +('20000000-0000-0000-0000-000000000010', 'Plug groß', + '{AKTIV} führt {PASSIV} einen großen Buttplug anal ein, dieser ist bis auf weiteres zu tragen.', + '{AKTIV}, es ist Zeit {PASSIV} von seinem Plug zu befreien', + 10, 30, '10000000-0000-0000-0000-000000000003'), + +('20000000-0000-0000-0000-000000000011', 'Elektro-Plug anal', + '{AKTIV} führt {PASSIV} einen Elekro-Plug anal ein, dieser ist bis auf weiteres zu tragen. {AKTIV} darf {PASSIV} leichte Stromstöße verpassen', + '{AKTIV}, es ist Zeit {PASSIV} von seinem Plug zu befreien', + 10, 30, '10000000-0000-0000-0000-000000000003'), + +('20000000-0000-0000-0000-000000000012', 'Elektro-Plug vaginal', + '{AKTIV} führt {PASSIV} einen Elekto-Plug vaginal ein, dieser ist bis auf weiteres zu tragen. {AKTIV} darf {PASSIV} leichte Stromstöße verpassen', + '{AKTIV}, es ist Zeit {PASSIV} von seinem Plug zu befreien', + 10, 30, '10000000-0000-0000-0000-000000000003'); + +-- Gruppe: Knebel +INSERT IGNORE INTO sperre (sperre_id, kurz_text, text, release_text, minuten_von, minuten_bis, gruppe_id) VALUES +('20000000-0000-0000-0000-000000000013', 'Ballknebel', + '{AKTIV}, lege {PASSIV} einen Ballknebel an, dieser ist bis auf weiteres zu tragen.', + '{AKTIV}, es ist Zeit {PASSIV} von seinem Knebel zu befreien.', + 10, 30, '10000000-0000-0000-0000-000000000004'), + +('20000000-0000-0000-0000-000000000014', 'Penisknebel', + '{AKTIV}, lege {PASSIV} einen Dildoknebel an, dieser ist bis auf weiteres zu tragen.', + '{AKTIV}, es ist Zeit {PASSIV} von seinem Knebel zu befreien.', + 10, 30, '10000000-0000-0000-0000-000000000004'), + +('20000000-0000-0000-0000-000000000015', 'Aufblasbarer Knebel', + '{AKTIV}, lege {PASSIV} einen aufblasbaren Knebel an und pumpe diesen soweit auf, dass {PASSIV} noch halbwegs gut atmen kann, dieser ist bis auf weiteres zu tragen.', + '{AKTIV}, es ist Zeit {PASSIV} von seinem Knebel zu befreien.', + 5, 15, '10000000-0000-0000-0000-000000000004'), + +('20000000-0000-0000-0000-000000000016', 'Isolationsmaske', + '{AKTIV}, lege {PASSIV} eine Isolationsmaske an, diese ist bis auf weiteres zu tragen.', + '{AKTIV}, es ist Zeit {PASSIV} von seinem Knebel zu befreien.', + 5, 15, '10000000-0000-0000-0000-000000000004'); + +-- sperre_sperre_fuer (war @CollectionTable name="sperre_sperreFuer" → snake_case) +INSERT IGNORE INTO sperre_sperre_fuer (sperre_id, werkzeug) VALUES +('20000000-0000-0000-0000-000000000001', 'VAGINA'), +('20000000-0000-0000-0000-000000000002', 'VAGINA'), +('20000000-0000-0000-0000-000000000003', 'VAGINA'), +('20000000-0000-0000-0000-000000000003', 'ANUS'), +('20000000-0000-0000-0000-000000000004', 'VAGINA'), +('20000000-0000-0000-0000-000000000004', 'ANUS'), +('20000000-0000-0000-0000-000000000005', 'PENIS'), +('20000000-0000-0000-0000-000000000006', 'PENIS'), +('20000000-0000-0000-0000-000000000007', 'PENIS'), +('20000000-0000-0000-0000-000000000007', 'ANUS'), +('20000000-0000-0000-0000-000000000008', 'ANUS'), +('20000000-0000-0000-0000-000000000009', 'ANUS'), +('20000000-0000-0000-0000-000000000010', 'ANUS'), +('20000000-0000-0000-0000-000000000011', 'ANUS'), +('20000000-0000-0000-0000-000000000012', 'VAGINA'), +('20000000-0000-0000-0000-000000000013', 'MUND'), +('20000000-0000-0000-0000-000000000014', 'MUND'), +('20000000-0000-0000-0000-000000000015', 'MUND'), +('20000000-0000-0000-0000-000000000016', 'MUND'); + + +-- ── Strafen ────────────────────────────────────────────────── +INSERT IGNORE INTO strafe (strafe_id, kurz_text, text, level, sekunden_von, sekunden_bis, gruppe_id) VALUES +('30000000-0000-0000-0000-000000000001', '5 Schläge mit flachen Hand', + '{PASSIV} stellt sich mit dem Gesicht zur Wand, Hände hinterm Kopf, Beine schulterbreit, {AKTIV} verpasst {PASSIV} 5 Schläge mit der flachen Hand auf das Gesäß.', + 1, NULL, NULL, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000002', '15 Schläge mit flachen Hand', + '{PASSIV} stellt sich mit dem Gesicht zur Wand, Hände hinterm Kopf, Beine schulterbreit, {AKTIV} verpasst {PASSIV} 15 beherzte Schläge mit der flachen Hand auf das Gesäß, {PASSIV} zählt laut mit', + 3, NULL, NULL, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000003', '5 Schläge mit Gerte', + '{PASSIV} stellt sich mit dem Gesicht zur Wand, Hände hinterm Kopf, Beine schulterbreit, {AKTIV} verpasst {PASSIV} 5 Schläge mit der Gerte auf das Gesäß.', + 2, NULL, NULL, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000004', '15 Schläge mit Gerte', + '{PASSIV} stellt sich mit dem Gesicht zur Wand, Hände hinterm Kopf, Beine schulterbreit, {AKTIV} verpasst {PASSIV} 15 beherzte Schläge mit der Gerte auf das Gesäß, {PASSIV} zählt laut mit', + 4, NULL, NULL, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000005', '5 Schläge mit Paddel', + '{PASSIV} stellt sich mit dem Gesicht zur Wand, Hände hinterm Kopf, Beine schulterbreit, {AKTIV} verpasst {PASSIV} 5 Schläge mit dem Paddel auf das Gesäß.', + 2, NULL, NULL, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000006', '15 Schläge mit Paddel', + '{PASSIV} stellt sich mit dem Gesicht zur Wand, Hände hinterm Kopf, Beine schulterbreit, {AKTIV} verpasst {PASSIV} 15 beherzte Schläge mit dem Paddel auf das Gesäß, {PASSIV} zählt laut mit', + 4, NULL, NULL, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000007', '5 Schläge mit Peitsche', + '{PASSIV} stellt sich mit dem Gesicht zur Wand, Hände hinterm Kopf, Beine schulterbreit, {AKTIV} verpasst {PASSIV} 5 Schläge mit der Peitsche auf das Gesäß.', + 3, NULL, NULL, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000008', '15 Schläge mit Peitsche', + '{PASSIV} stellt sich mit dem Gesicht zur Wand, Hände hinterm Kopf, Beine schulterbreit, {AKTIV} verpasst {PASSIV} 15 beherzte Schläge mit der Peitsche auf das Gesäß, {PASSIV} zählt laut mit', + 5, NULL, NULL, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000009', 'Schläge auf Klitoris mit Hand', + '{PASSIV} liegt auf dem Rücken mit breiten Beinen, {AKTIV} verpasst {PASSIV} 5 Schläge mit der Hand auf die Klitoris, {PASSIV} zählt laut mit', + 4, NULL, NULL, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000010', 'Schläge auf Klitoris mit Peitsche', + '{PASSIV} liegt auf dem Rücken mit breiten Beinen, {AKTIV} verpasst {PASSIV} 5 Schläge mit der Peitsche auf die Klitoris, {PASSIV} zählt laut mit', + 5, NULL, NULL, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000011', 'Schläge auf Klitoris mit Paddel', + '{PASSIV} liegt auf dem Rücken mit breiten Beinen, {AKTIV} verpasst {PASSIV} 5 Schläge mit dem Paddel auf die Klitoris, {PASSIV} zählt laut mit', + 5, NULL, NULL, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000012', 'Schläge auf Klitoris mit Gerte', + '{PASSIV} liegt auf dem Rücken mit breiten Beinen, {AKTIV} verpasst {PASSIV} 5 Schläge mit der Gerte auf die Klitoris, {PASSIV} zählt laut mit', + 5, NULL, NULL, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000013', '5 Ohrfeigen', + '{PASSIV} stellt sich mit dem Rücken zur Wand, Hände hinterm Kopf, Beine schulterbreit, {AKTIV} verpasst {PASSIV} 5 Ohrfeigen, {PASSIV} zählt laut mit', + 5, NULL, NULL, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000014', 'Elektroplug anal', + '{AKTIV} führt {PASSIV} anal einen Elektro-Plug ein. {AKTIV} erhöht ganz langsam die Intensität bis {PASSIV} ''STOP'' sagt, dann fängt {AKTIV} wieder bei null an', + 5, 30, 90, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000015', 'Elektroplug vaginal', + '{AKTIV} führt {PASSIV} vaginal einen Elektro-Plug ein. {AKTIV} erhöht ganz langsam die Intensität bis {PASSIV} ''STOP'' sagt, dann fängt {AKTIV} wieder bei null an', + 5, 30, 90, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000016', 'Pumpplug anal', + '{AKTIV} führt {PASSIV} anal einen Pump-Plug ein. {AKTIV} pumpt ganz langsam auf bis {PASSIV} ''STOP'' sagt, dann fängt {AKTIV} wieder bei null an', + 5, 30, 90, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000017', 'Pumpplug vaginal', + '{AKTIV} führt {PASSIV} vaginal einen Pump-Plug ein. {AKTIV} pumpt ganz langsam auf bis {PASSIV} ''STOP'' sagt, dann fängt {AKTIV} wieder bei null an', + 5, 30, 90, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000018', 'Facesitting (Vagina)', + '{PASSIV} liegt auf dem Rücken, {AKTIV} setzt sich auf das Gesicht von {PASSIV} und lässt sich den Vaginal und/oder Analbereich verwöhnen', + 2, 90, 180, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000019', 'Facesitting gefesselt (Vagina)', + '{PASSIV} liegt mit auf den Rücken gefesselten Händen auf dem Rücken, {AKTIV} setzt sich auf das Gesicht von {PASSIV} und lässt sich den Vaginal und/oder Analbereich verwöhnen', + 4, 90, 180, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000020', 'Facesitting (Penis)', + '{PASSIV} liegt auf dem Rücken, {AKTIV} setzt sich auf das Gesicht von {PASSIV} und lässt sich den Penis und/oder Analbereich verwöhnen', + 2, 90, 180, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000021', 'Facesitting gefesselt (Penis)', + '{PASSIV} liegt mit auf den Rücken gefesselten Händen auf dem Rücken, {AKTIV} setzt sich auf das Gesicht von {PASSIV} und lässt sich den Penis und/oder Analbereich verwöhnen', + 4, 90, 180, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000022', 'Facesitting Doppelpenisknebel', + '{PASSIV} liegt auf dem Rücken, {AKTIV} legt {PASSIV} einen Doppel-Penisknebel an und reitet diesen vaginal oder anal', + 3, 60, 120, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000023', 'Facesitting Doppelpenisknebel gefesselt', + '{PASSIV} liegt mit auf den Rücken gefesselten Händen auf dem Rücken, {AKTIV} legt {PASSIV} einen Doppel-Penisknebel an und reitet diesen vaginal oder anal', + 3, 60, 120, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000024', 'Nippelklemmen', + '{AKTIV} legt {PASSIV} Nippelklemmen an, {AKTIV} zieht an der Kette und erhöht ganz langsam die Intensität bis {PASSIV} ''STOP'' sagt, dann fängt {AKTIV} wieder bei null an', + 3, 30, 90, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000025', 'Nippelbehandlung', + '{AKTIV} nimmt die Nippel von {PASSIV} zwischen die Finger und erhöht langsam den Druck bis {PASSIV} ''STOP'' sagt', + 2, NULL, NULL, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000026', 'Hilflos liegen lassen', + '{AKTIV} fesselt, knebelt und verbindet die Augen von {PASSIV}. {AKTIV} lässt {PASSIV} wehrlos liegen, bei Ablauf der Zeit erlöst {AKTIV} {PASSIV} mit einem beherzten Platsch auf den Po', + 4, 300, 600, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000027', 'Strapon reiten', + '{PASSIV} liegt auf dem Rücken und trägt dabei einen Umschnalldildo. {AKTIV} reitet den Umschnalldildo von {PASSIV}', + 3, 60, 180, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000028', 'Strapon reiten gefesselt', + '{AKTIV} fesselt und knebelt {PASSIV}. {PASSIV} trägt dabei einen Umschnalldildo. {AKTIV} reitet den Umschnalldildo von {PASSIV}', + 4, 60, 180, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000029', 'Teaseblowjob mit dem Strapon', + '{AKTIV} fesselt und knebelt {PASSIV}. {PASSIV} trägt dabei einen Umschnalldildo, KG und einen großen Buttplug. {AKTIV} gibt dem Umschnalldildo einen Blowjob in 69er Position und präsentiert {PASSIV} dabei den Intimbereich', + 5, 180, 300, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000030', 'Teasereiten mit Strapon', + '{AKTIV} fesselt und knebelt {PASSIV}. {PASSIV} trägt dabei einen Umschnalldildo, KG und einen großen Buttplug. {AKTIV} reitet den Umschnalldildo von {PASSIV}.', + 5, 180, 300, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000031', 'Tease mit Selbstbefriedigung (Mann KG)', + '{AKTIV} knebelt und fesselt {PASSIV} an einen Stuhl. {PASSIV} trägt dabei einen KG und einen großen Buttplug. {AKTIV} befriedigt sich dann vor den Augen von {PASSIV} selber', + 4, 240, 360, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000032', 'Tease mit Selbstbefriedigung (Frau KG)', + '{AKTIV} knebelt und fesselt {PASSIV} an einen Stuhl. {PASSIV} trägt dabei einen KG und einen großen Buttplug. {AKTIV} befriedigt sich dann vor den Augen von {PASSIV} selber', + 4, 240, 360, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000033', 'Blowjob auf allen vieren', + '{AKTIV}, zwinge {PASSIV} vor dir auf die Knie, führe dein Glied (oder Strap on) in den Mund von {PASSIV} ein und zeig mit einem Deepthroat, wer das sagen hat', + 5, 30, 90, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000034', 'Oralsex mit kleinem Dildo in der Vagina', + '{PASSIV}, geh auf die Knie und reite vaginal einen kleinen Dildo, befriedige dabei {AKTIV} oral.', + 2, 60, 120, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000035', 'Oralsex mit großen Dildo in der Vagina', + '{PASSIV}, geh auf die Knie und reite vaginal einen großen Dildo, befriedige dabei {AKTIV} oral.', + 4, 60, 120, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000036', 'Oralsex mit kleinem Dildo im Anus', + '{PASSIV}, geh auf die Knie und reite anal einen kleinen Dildo, befriedige dabei {AKTIV} oral.', + 3, 60, 120, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000037', 'Oralsex mit großen Dildo im Anus', + '{PASSIV}, geh auf die Knie und reite anal einen großen Dildo, befriedige dabei {AKTIV} oral.', + 4, 60, 120, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000038', 'Vagina dehnen', + '{PASSIV} geht auf alle viere und streckt den Hintern schön in die Luft, {AKTIV} führe langsam nach und nach mehr Finger in die Vagina von {PASSIV} ein, bis {PASSIV} ''STOP'' sagt', + 2, NULL, NULL, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000039', 'Anus dehnen', + '{PASSIV} geht auf alle viere und streckt den Hintern schön in die Luft, {AKTIV} führe langsam nach und nach mehr Finger in die Anus von {PASSIV} ein, bis {PASSIV} ''STOP'' sagt', + 2, NULL, NULL, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000040', 'Vaginalsex in Missionarstellung und Breathplay', + '{AKTIV} dringt in Missionarsstellung in {PASSIV} und gibt vollgas, dabei packt {AKTIV} {PASSIV} am Hals und drückt beherzt zu', + 4, 30, 60, '10000000-0000-0000-0000-000000000005'), + +('30000000-0000-0000-0000-000000000041', 'Analsex in Missionarstellung und Breathplay', + '{AKTIV} dringt in Missionarsstellung anal in {PASSIV} und gibt vollgas, dabei packt {AKTIV} {PASSIV} am Hals und drückt beherzt zu', + 4, 30, 60, '10000000-0000-0000-0000-000000000005'); + +-- strafe_benoetigt_passiv (war @CollectionTable name="strafe_benoetigtPassiv") +INSERT IGNORE INTO strafe_benoetigt_passiv (strafe_id, werkzeug) VALUES +('30000000-0000-0000-0000-000000000009', 'VAGINA'), +('30000000-0000-0000-0000-000000000010', 'VAGINA'), +('30000000-0000-0000-0000-000000000011', 'VAGINA'), +('30000000-0000-0000-0000-000000000012', 'VAGINA'), +('30000000-0000-0000-0000-000000000014', 'ANUS'), +('30000000-0000-0000-0000-000000000015', 'VAGINA'), +('30000000-0000-0000-0000-000000000016', 'ANUS'), +('30000000-0000-0000-0000-000000000017', 'VAGINA'), +('30000000-0000-0000-0000-000000000018', 'MUND'), +('30000000-0000-0000-0000-000000000019', 'MUND'), +('30000000-0000-0000-0000-000000000020', 'MUND'), +('30000000-0000-0000-0000-000000000021', 'MUND'), +('30000000-0000-0000-0000-000000000022', 'MUND'), +('30000000-0000-0000-0000-000000000023', 'MUND'), +('30000000-0000-0000-0000-000000000033', 'MUND'), +('30000000-0000-0000-0000-000000000034', 'VAGINA'), +('30000000-0000-0000-0000-000000000035', 'VAGINA'), +('30000000-0000-0000-0000-000000000036', 'ANUS'), +('30000000-0000-0000-0000-000000000037', 'ANUS'), +('30000000-0000-0000-0000-000000000038', 'VAGINA'), +('30000000-0000-0000-0000-000000000039', 'ANUS'), +('30000000-0000-0000-0000-000000000040', 'VAGINA'), +('30000000-0000-0000-0000-000000000041', 'ANUS'); + +-- strafe_benoetigt_aktiv (war @CollectionTable name="strafe_benoetigtAktiv") +INSERT IGNORE INTO strafe_benoetigt_aktiv (strafe_id, werkzeug) VALUES +('30000000-0000-0000-0000-000000000018', 'VAGINA'), +('30000000-0000-0000-0000-000000000018', 'ANUS'), +('30000000-0000-0000-0000-000000000019', 'VAGINA'), +('30000000-0000-0000-0000-000000000019', 'ANUS'), +('30000000-0000-0000-0000-000000000020', 'PENIS'), +('30000000-0000-0000-0000-000000000020', 'ANUS'), +('30000000-0000-0000-0000-000000000021', 'VAGINA'), +('30000000-0000-0000-0000-000000000021', 'PENIS'), +('30000000-0000-0000-0000-000000000022', 'VAGINA'), +('30000000-0000-0000-0000-000000000023', 'VAGINA'), +('30000000-0000-0000-0000-000000000027', 'VAGINA'), +('30000000-0000-0000-0000-000000000027', 'ANUS'), +('30000000-0000-0000-0000-000000000028', 'VAGINA'), +('30000000-0000-0000-0000-000000000028', 'ANUS'), +('30000000-0000-0000-0000-000000000029', 'VAGINA'), +('30000000-0000-0000-0000-000000000030', 'VAGINA'), +('30000000-0000-0000-0000-000000000031', 'VAGINA'), +('30000000-0000-0000-0000-000000000032', 'PENIS'), +('30000000-0000-0000-0000-000000000033', 'PENIS'), +('30000000-0000-0000-0000-000000000033', 'UMSCHNALLDILDO'), +('30000000-0000-0000-0000-000000000034', 'VAGINA'), +('30000000-0000-0000-0000-000000000034', 'PENIS'), +('30000000-0000-0000-0000-000000000035', 'VAGINA'), +('30000000-0000-0000-0000-000000000035', 'PENIS'), +('30000000-0000-0000-0000-000000000036', 'VAGINA'), +('30000000-0000-0000-0000-000000000036', 'PENIS'), +('30000000-0000-0000-0000-000000000037', 'VAGINA'), +('30000000-0000-0000-0000-000000000037', 'PENIS'), +('30000000-0000-0000-0000-000000000040', 'PENIS'), +('30000000-0000-0000-0000-000000000040', 'UMSCHNALLDILDO'), +('30000000-0000-0000-0000-000000000041', 'PENIS'), +('30000000-0000-0000-0000-000000000041', 'UMSCHNALLDILDO'); + + +-- ── Aufgaben ───────────────────────────────────────────────── +INSERT IGNORE INTO aufgabe (aufgabe_id, kurz_text, text, level, sekunden_von, sekunden_bis, gruppe_id) VALUES +('40000000-0000-0000-0000-000000000001', 'Hintern präsentieren', + '{AKTIV}, zeig {PASSIV} deinen Hintern, gib dir selber dabei ein oder zwei Klappse auf den Po', + 1, NULL, NULL, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000002', 'Hals küssen', + '{AKTIV}, küsse den Hals von {PASSIV} leidenschaftlich', + 1, 30, 60, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000003', 'Bauchnabel küssen', + '{AKTIV}, zeichne mit Küssen den Bauchnabel von {PASSIV} nach', + 1, 30, 60, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000004', 'Ohren knabbern', + '{AKTIV}, knabber leidenschaftlich an den Ohrläppchen von {PASSIV}', + 1, 30, 60, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000005', 'Berühren ohne anfassen', + '{AKTIV}, berühre den gesamten Körper von {PASSIV} ohne die Hände zu verwenden', + 2, 60, 120, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000006', 'Nacken küssen', + '{PASSIV} sitzt vor {AKTIV}, {AKTIV} küsste leidenschaftlich den Nacken von {PASSIV}', + 1, 60, 120, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000007', 'Brust küssen', + '{AKTIV}, küsse die Brust von {PASSIV} ohne die Nippel zu berühren', + 1, 60, 120, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000008', 'Nippel verwöhnen', + '{AKTIV}, verwöhne die Nippel von {PASSIV} mit Küssen', + 2, 60, 120, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000009', 'Hintern küssen', + '{AKTIV}, küsse den Hintern von {PASSIV} ohne den Anus zu berühren', + 1, 60, 120, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000010', 'Intimkuss durch Unterwäsche', + '{AKTIV}, küsse den Intimbereich von {PASSIV} durch die Unterwäsche', + 2, 60, 120, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000011', 'Brustmassage', + '{AKTIV}, massiere die Brust von {PASSIV} leidenschaftlich', + 1, 60, 120, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000012', 'Hinternmassage', + '{AKTIV}, massiere den Hintern von {PASSIV} leidenschaftlich', + 1, 60, 120, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000013', 'Rückenmassage', + '{AKTIV}, massiere den Rücken von {PASSIV} leidenschaftlich', + 1, 60, 120, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000014', 'Oberschenkelmassage', + '{AKTIV}, massiere die Oberschenkel von {PASSIV} leidenschaftlich', + 1, 60, 120, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000015', 'Klitoris mit Vibrator verwöhnen', + '{AKTIV}, verwöhne die Klitoris von {PASSIV} mit einem Vibrator', + 3, 30, 180, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000016', 'Cunnilingus und Finger in Vagina', + '{AKTIV}, verwöhne die Klitoris von {PASSIV} mit dem Mund, führe dabei einen bis zwei Finger in die Vagina von {PASSIV} ein', + 3, 30, 180, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000017', 'Klitoris mit Fingern verwöhnen und Finger in Vagina', + '{AKTIV}, verwöhne die Klitoris von {PASSIV} mit der Hand, führe dabei einen bis zwei Finger in die Vagina von {PASSIV} ein', + 4, 30, 180, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000018', 'Eichel mit Vibrator verwöhnen', + '{AKTIV}, verwöhne die Eichel von {PASSIV} mit einem Vibrator', + 3, 30, 180, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000019', 'Felatio', + '{AKTIV}, verwöhne die Eichel von {PASSIV} mit dem Mund', + 3, 30, 180, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000020', 'Handjob', + '{AKTIV}, verwöhne die Eichel von {PASSIV} mit der Hand', + 3, 30, 180, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000021', 'Facesitting', + '{AKTIV} liegt auf dem Rücken, {PASSIV} sitzt auf seinem Gesicht. {AKTIV}, verwöhne die Vagina von {PASSIV} mit dem Mund', + 4, 60, 180, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000022', '69er-Position', + '69er-Zeit: {AKTIV} liegt oben. {PASSIV}, falls du verschlossen bist, ziehe einen Strap on an, damit {AKTIV} auch was zu tun hat.', + 4, 60, 180, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000023', 'Kleiner Dildo vaginal', + '{AKTIV}, führe {PASSIV} einen kleinen Dildo vaginal ein und verwöhne {PASSIV} durch langsame Bewegungen mit selbigem', + 3, 30, 180, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000024', 'Großer Dildo vaginal', + '{AKTIV}, führe {PASSIV} einen großen Dildo vaginal ein und verwöhne {PASSIV} durch langsame Bewegungen mit selbigem', + 4, 30, 180, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000025', 'Großer Dildo vaginal schnell', + '{AKTIV}, führe {PASSIV} einen großen Dildo vaginal ein und bewege selbigen möglichst schnell rein und raus', + 5, 30, 60, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000026', 'Missionarstellung langsam', + '{AKTIV} dringt in Missionarstellung in {PASSIV} ein und verwöhnt {PASSIV} mit langsamen Bewegungen', + 3, 60, 180, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000027', 'Missionarstellung schnell', + '{AKTIV} dringt in Missionarstellung in {PASSIV} ein und verwöhnt {PASSIV} mit schnellen Bewegungen', + 4, 30, 90, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000028', 'Missionarstellung Vollgas', + '{AKTIV} dringt in Missionarstellung in {PASSIV} ein und gibt vollgas', + 5, 30, 60, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000029', 'Reiterstellung langsam', + '{PASSIV} setzt sich in Reiterstellung auf {AKTIV}. {PASSIV} bestimmt das Tempo', + 3, 60, 180, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000030', 'Reiterstellung schnell', + '{PASSIV} setzt sich in Reiterstellung auf {AKTIV}. {PASSIV} versucht das Tempo hoch zu halten', + 4, 60, 120, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000031', 'Reiterstellung vollgas', + '{PASSIV} setzt sich in Reiterstellung auf {AKTIV} und gibt vollgas', + 5, 30, 60, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000032', 'Doggystyle langsam', + '{AKTIV} dringt in Hundestellung in {PASSIV} ein und verwöhnt {PASSIV} mit langsamen Bewegungen', + 3, 60, 180, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000033', 'Doggystyle schnell', + '{AKTIV} dringt in Hundestellung in {PASSIV} ein und verwöhnt {PASSIV} mit schnellen Bewegungen', + 4, 60, 120, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000034', 'Doggystyle vollgas', + '{AKTIV} dringt in Hundestellung in {PASSIV} ein und gibt vollgas', + 5, 30, 60, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000035', 'Doggystyle vollgas keinen Mucks', + '{AKTIV} dringt in Hundestellung in {PASSIV} ein und gibt vollgas. {PASSIV} darf dabei keinen Laut von sich geben.', + 5, 30, 60, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000036', 'Doggystyle Tempo bestimmt die ''gefickte'' Person', + '{AKTIV} dringt in Hundestellung in {PASSIV} ein. {AKTIV} hält still und {PASSIV} gibt das Tempo vor', + 3, 60, 180, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000037', 'Löffelchen langsam', + '{AKTIV} dringt in Löffelchenstellung in {PASSIV} ein und verwöhnt {PASSIV} mit langsamen Bewegungen', + 3, 60, 180, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000038', 'Löffelchen schnell', + '{AKTIV} dringt in Löffelchenstellung in {PASSIV} ein und verwöhnt {PASSIV} mit schnellen Bewegungen', + 4, 60, 120, '10000000-0000-0000-0000-000000000006'), + +('40000000-0000-0000-0000-000000000039', 'Löffelchen vollgas', + '{AKTIV} dringt in Löffelchenstellung in {PASSIV} ein und gibt vollgas', + 5, 30, 60, '10000000-0000-0000-0000-000000000006'); + +-- aufgabe_benoetigt_aktiv (war @CollectionTable name="aufgabe_benoetigtAktiv") +INSERT IGNORE INTO aufgabe_benoetigt_aktiv (aufgabe_id, werkzeug) VALUES +('40000000-0000-0000-0000-000000000002', 'MUND'), +('40000000-0000-0000-0000-000000000003', 'MUND'), +('40000000-0000-0000-0000-000000000004', 'MUND'), +('40000000-0000-0000-0000-000000000006', 'MUND'), +('40000000-0000-0000-0000-000000000007', 'MUND'), +('40000000-0000-0000-0000-000000000008', 'MUND'), +('40000000-0000-0000-0000-000000000009', 'MUND'), +('40000000-0000-0000-0000-000000000010', 'MUND'), +('40000000-0000-0000-0000-000000000016', 'MUND'), +('40000000-0000-0000-0000-000000000019', 'MUND'), +('40000000-0000-0000-0000-000000000021', 'MUND'), +('40000000-0000-0000-0000-000000000022', 'VAGINA'), +('40000000-0000-0000-0000-000000000022', 'MUND'), +('40000000-0000-0000-0000-000000000026', 'PENIS'), +('40000000-0000-0000-0000-000000000026', 'UMSCHNALLDILDO'), +('40000000-0000-0000-0000-000000000027', 'PENIS'), +('40000000-0000-0000-0000-000000000027', 'UMSCHNALLDILDO'), +('40000000-0000-0000-0000-000000000028', 'PENIS'), +('40000000-0000-0000-0000-000000000028', 'UMSCHNALLDILDO'), +('40000000-0000-0000-0000-000000000029', 'PENIS'), +('40000000-0000-0000-0000-000000000029', 'UMSCHNALLDILDO'), +('40000000-0000-0000-0000-000000000030', 'PENIS'), +('40000000-0000-0000-0000-000000000030', 'UMSCHNALLDILDO'), +('40000000-0000-0000-0000-000000000031', 'PENIS'), +('40000000-0000-0000-0000-000000000031', 'UMSCHNALLDILDO'), +('40000000-0000-0000-0000-000000000032', 'PENIS'), +('40000000-0000-0000-0000-000000000032', 'UMSCHNALLDILDO'), +('40000000-0000-0000-0000-000000000033', 'PENIS'), +('40000000-0000-0000-0000-000000000033', 'UMSCHNALLDILDO'), +('40000000-0000-0000-0000-000000000034', 'PENIS'), +('40000000-0000-0000-0000-000000000034', 'UMSCHNALLDILDO'), +('40000000-0000-0000-0000-000000000035', 'PENIS'), +('40000000-0000-0000-0000-000000000035', 'UMSCHNALLDILDO'), +('40000000-0000-0000-0000-000000000036', 'PENIS'), +('40000000-0000-0000-0000-000000000036', 'UMSCHNALLDILDO'), +('40000000-0000-0000-0000-000000000037', 'PENIS'), +('40000000-0000-0000-0000-000000000037', 'UMSCHNALLDILDO'), +('40000000-0000-0000-0000-000000000038', 'PENIS'), +('40000000-0000-0000-0000-000000000038', 'UMSCHNALLDILDO'), +('40000000-0000-0000-0000-000000000039', 'PENIS'), +('40000000-0000-0000-0000-000000000039', 'UMSCHNALLDILDO'); + +-- aufgabe_benoetigt_passiv (war @CollectionTable name="aufgabe_benoetigtPassiv") +INSERT IGNORE INTO aufgabe_benoetigt_passiv (aufgabe_id, werkzeug) VALUES +('40000000-0000-0000-0000-000000000015', 'VAGINA'), +('40000000-0000-0000-0000-000000000016', 'VAGINA'), +('40000000-0000-0000-0000-000000000017', 'VAGINA'), +('40000000-0000-0000-0000-000000000018', 'PENIS'), +('40000000-0000-0000-0000-000000000019', 'PENIS'), +('40000000-0000-0000-0000-000000000020', 'PENIS'), +('40000000-0000-0000-0000-000000000021', 'VAGINA'), +('40000000-0000-0000-0000-000000000022', 'MUND'), +('40000000-0000-0000-0000-000000000023', 'VAGINA'), +('40000000-0000-0000-0000-000000000024', 'VAGINA'), +('40000000-0000-0000-0000-000000000025', 'VAGINA'), +('40000000-0000-0000-0000-000000000026', 'VAGINA'), +('40000000-0000-0000-0000-000000000027', 'VAGINA'), +('40000000-0000-0000-0000-000000000028', 'VAGINA'), +('40000000-0000-0000-0000-000000000029', 'VAGINA'), +('40000000-0000-0000-0000-000000000030', 'VAGINA'), +('40000000-0000-0000-0000-000000000031', 'VAGINA'), +('40000000-0000-0000-0000-000000000032', 'VAGINA'), +('40000000-0000-0000-0000-000000000033', 'VAGINA'), +('40000000-0000-0000-0000-000000000034', 'VAGINA'), +('40000000-0000-0000-0000-000000000035', 'VAGINA'), +('40000000-0000-0000-0000-000000000036', 'VAGINA'), +('40000000-0000-0000-0000-000000000037', 'VAGINA'), +('40000000-0000-0000-0000-000000000038', 'VAGINA'), +('40000000-0000-0000-0000-000000000039', 'VAGINA'); diff --git a/testdaten.sql b/xxxthegame/src/main/resources/sql/testdaten.sql similarity index 100% rename from testdaten.sql rename to xxxthegame/src/main/resources/sql/testdaten.sql diff --git a/xxxthegame/src/main/resources/static/admin/admin.html b/xxxthegame/src/main/resources/static/admin/admin.html index 03c4544..9e7addc 100644 --- a/xxxthegame/src/main/resources/static/admin/admin.html +++ b/xxxthegame/src/main/resources/static/admin/admin.html @@ -194,6 +194,15 @@ font-size:0.78rem; color:var(--color-muted); line-height:1.6; } .placeholder-hint code { background:rgba(233,69,96,0.12); color:var(--color-primary); border-radius:3px; padding:0.05rem 0.3rem; font-size:0.75rem; } + #iTextAC { position:fixed; z-index:9999; background:var(--color-surface,#1e1e2e); + border:1px solid var(--color-border,#444); border-radius:6px; + box-shadow:0 4px 14px rgba(0,0,0,.5); display:none; overflow:hidden; min-width:180px; max-height:280px; overflow-y:auto; } + .ac-item { padding:0.45rem 0.9rem; cursor:pointer; font-size:0.88rem; + font-family:monospace; color:var(--color-text,#cdd6f4); user-select:none; } + .ac-item:hover, .ac-item-active { background:var(--color-primary,#cba6f7); color:#1e1e2e; } + .ac-separator { padding:0.2rem 0.7rem; font-size:0.72rem; color:var(--color-muted); + text-transform:uppercase; letter-spacing:0.05em; background:rgba(255,255,255,0.04); + pointer-events:none; border-top:1px solid rgba(136,136,136,0.2); margin-top:2px; } .modal-two-col { display:flex; gap:0.75rem; } .modal-two-col > * { flex:1; } .werkzeug-checks { display:flex; flex-wrap:nowrap; gap:0.25rem; margin-top:0.5rem; } @@ -315,6 +324,7 @@ +
@@ -430,6 +440,23 @@
+ + +
@@ -926,7 +953,7 @@ function renderAdminGruppen(gruppen) { ${renderSubSection('Aufgaben', sortByLevelThenName(g.aufgaben || []), 'aufgabe', renderAufgabe, g.gruppenId)} ${renderSubSection('Strafen', sortByLevelThenName(g.strafen || []), 'strafe', renderStrafe, g.gruppenId)} ${renderSubSection('Zeitstrafen', sortByName(g.sperren || []), 'zeitstrafe', renderZeitstrafe, g.gruppenId)} - ${renderSubSection('Finisher', sortByName(g.finisher || []), 'finisher', renderFinisher, g.gruppenId)} + ${renderSubSection('Finisher', sortByGeschlecht(g.finisher || []), 'finisher', renderFinisher, g.gruppenId)}
`; }).join(''); @@ -1010,6 +1037,7 @@ function itemCard(id, kurzText, badges, rows, kind, gruppenId) { const actionBtns = `
+
`; return `
@@ -1073,6 +1101,52 @@ async function duplicateItem(kind, itemId, gruppenId, event) { else document.getElementById('gruppeActionError').textContent = 'Fehler beim Duplizieren (HTTP ' + r.status + ').'; } +let _moveState = null; +function openMoveModal(kind, itemId, currentGruppeId, event) { + event.stopPropagation(); + const item = _itemData[itemId]; if (!item) return; + _moveState = { kind, itemId, currentGruppeId }; + document.getElementById('moveModalItemName').textContent = item.kurzText || itemId; + document.getElementById('moveModalError').textContent = ''; + const sel = document.getElementById('moveModalSelect'); + sel.innerHTML = ''; + Object.values(_gruppeData) + .filter(g => g.gruppenId !== currentGruppeId) + .sort((a, b) => (a.name || '').localeCompare(b.name || '', 'de')) + .forEach(g => { + const opt = document.createElement('option'); + opt.value = g.gruppenId; + opt.textContent = g.name; + sel.appendChild(opt); + }); + document.getElementById('moveModal').classList.add('open'); +} +function closeMoveModal() { + document.getElementById('moveModal').classList.remove('open'); + _moveState = null; +} +document.getElementById('moveModalCancel').addEventListener('click', closeMoveModal); +document.getElementById('moveModal').addEventListener('click', e => { + if (e.target === document.getElementById('moveModal')) closeMoveModal(); +}); +document.getElementById('moveModalOk').addEventListener('click', async () => { + if (!_moveState) return; + const { kind, itemId, currentGruppeId } = _moveState; + const targetGruppeId = document.getElementById('moveModalSelect').value; + if (!targetGruppeId) return; + const r = await fetch(`/admin/aufgabengruppen/items/${kind}/${itemId}/move?targetGruppeId=${targetGruppeId}`, { + method: 'PUT' + }); + if (r.ok || r.status === 204) { + closeMoveModal(); + openItemId = null; + pendingExpandId = currentGruppeId; + loadAdminGruppen(); + } else { + document.getElementById('moveModalError').textContent = 'Fehler beim Verschieben (HTTP ' + r.status + ').'; + } +}); + function sortByLevelThenName(items) { return items.slice().sort((a, b) => { const la = a.level ?? 999, lb = b.level ?? 999; @@ -1081,6 +1155,14 @@ function sortByLevelThenName(items) { }); } function sortByName(items) { return items.slice().sort((a, b) => (a.kurzText || '').localeCompare(b.kurzText || '', 'de')); } +const GESCHLECHT_ORDER = { WEIBLICH: 0, DIVERS: 1, MAENNLICH: 2 }; +function sortByGeschlecht(items) { + return items.slice().sort((a, b) => { + const ga = GESCHLECHT_ORDER[a.geschlecht] ?? 99, gb = GESCHLECHT_ORDER[b.geschlecht] ?? 99; + if (ga !== gb) return ga - gb; + return (a.kurzText || '').localeCompare(b.kurzText || '', 'de'); + }); +} function formatSek(von, bis) { if (von != null && bis != null) return `${von}–${bis} s`; if (von != null) return `ab ${von} s`; if (bis != null) return `bis ${bis} s`; return ''; @@ -1308,8 +1390,116 @@ function openEditItemModal(itemId, event) { itemModal.classList.add('open'); document.getElementById('iKurzText').focus(); } -function closeItemModal() { itemModal.classList.remove('open'); closeToySearch(); document.getElementById('iPlaceholderHint').style.display = 'none'; } +function closeItemModal() { itemModal.classList.remove('open'); closeToySearch(); document.getElementById('iPlaceholderHint').style.display = 'none'; document.getElementById('iTextAC').style.display = 'none'; } function togglePlaceholderHint() { const el = document.getElementById('iPlaceholderHint'); el.style.display = el.style.display === 'none' ? 'block' : 'none'; } + +(function() { + const STATIC = ['{AKTIV}', '{PASSIV}']; + const ta = document.getElementById('iText'); + const ac = document.getElementById('iTextAC'); + let _allToys = []; + let _items = []; + let _wordStart = 0; + let activeIdx = -1; + + function currentWord() { + const pos = ta.selectionStart; + let start = pos; + while (start > 0 && !/\s/.test(ta.value[start - 1])) start--; + return { word: ta.value.slice(start, pos).toLowerCase(), start }; + } + function buildItems(filter) { + const f = filter || ''; + _items = STATIC.filter(s => !f || s.toLowerCase().includes(f)).map(s => ({ label: s, insert: s })); + const toys = _allToys.filter(t => !f || t.name.toLowerCase().includes(f)); + if (toys.length) { + _items.push({ separator: true, label: 'Toys' }); + toys.forEach(t => _items.push({ label: t.name, insert: t.name, toyId: t.toyId })); + } + } + function selectables() { return _items.map((it, i) => it.separator ? null : i).filter(i => i !== null); } + function renderItems() { + ac.innerHTML = ''; + activeIdx = -1; + _items.forEach((item, i) => { + if (item.separator) { + const sep = document.createElement('div'); + sep.className = 'ac-separator'; + sep.textContent = item.label; + ac.appendChild(sep); + } else { + const div = document.createElement('div'); + div.className = 'ac-item'; + div.dataset.idx = String(i); + div.textContent = item.label; + div.addEventListener('mousedown', e => { e.preventDefault(); doInsert(item); }); + div.addEventListener('mouseover', () => setActive(i)); + ac.appendChild(div); + } + }); + const s = selectables(); + if (s.length) { setActive(s[0]); ac.style.display = 'block'; } else hideAC(); + } + function showAC(toys) { + _allToys = toys || []; + const { word, start } = currentWord(); + _wordStart = start; + buildItems(word); + const rect = ta.getBoundingClientRect(); + ac.style.left = rect.left + 'px'; + ac.style.top = (rect.bottom + 4) + 'px'; + renderItems(); + } + function hideAC() { ac.style.display = 'none'; activeIdx = -1; } + function setActive(i) { + activeIdx = i; + let activeEl = null; + ac.querySelectorAll('.ac-item').forEach(el => { + const on = parseInt(el.dataset.idx) === i; + el.classList.toggle('ac-item-active', on); + if (on) activeEl = el; + }); + if (activeEl) activeEl.scrollIntoView({ block: 'nearest' }); + } + function doInsert(item) { + const end = ta.selectionStart; + ta.value = ta.value.slice(0, _wordStart) + item.insert + ta.value.slice(end); + ta.selectionStart = ta.selectionEnd = _wordStart + item.insert.length; + ta.focus(); + if (item.toyId) { + const already = (_selectedToys || []).find(t => t.toyId === item.toyId); + if (!already) toggleToyFromSearch(item.toyId); + } + hideAC(); + } + ta.addEventListener('keydown', e => { + if (e.ctrlKey && e.code === 'Space') { + e.preventDefault(); + _loadAvailableToys().then(toys => showAC(toys)).catch(() => showAC([])); + return; + } + if (ac.style.display !== 'block') return; + if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); hideAC(); return; } + const s = selectables(); + if (!s.length) return; + const pos = s.indexOf(activeIdx); + if (e.key === 'ArrowDown') { e.preventDefault(); setActive(s[(pos + 1) % s.length]); } + else if (e.key === 'ArrowUp') { e.preventDefault(); setActive(s[(pos - 1 + s.length) % s.length]); } + else if (e.key === 'Enter' || e.key === 'Tab') { + e.preventDefault(); + const item = _items[activeIdx]; + if (item && !item.separator) doInsert(item); + } + }); + ta.addEventListener('input', () => { + if (ac.style.display !== 'block') return; + const { word, start } = currentWord(); + _wordStart = start; + buildItems(word); + renderItems(); + }); + document.addEventListener('mousedown', e => { if (!ac.contains(e.target) && e.target !== ta) hideAC(); }); +})(); document.getElementById('itemCancelBtn').addEventListener('click', closeItemModal); itemModal.addEventListener('click', e => { if (e.target === itemModal) closeItemModal(); }); diff --git a/xxxthegame/src/main/resources/static/css/style.css b/xxxthegame/src/main/resources/static/css/style.css index dd9e32a..d968589 100644 --- a/xxxthegame/src/main/resources/static/css/style.css +++ b/xxxthegame/src/main/resources/static/css/style.css @@ -883,7 +883,8 @@ body.app { } /* Benachrichtigungen */ -.topbar-notif-item--unread { background: rgba(var(--color-primary-rgb, 231,57,84), 0.04); } +.topbar-notif-item--unread { background: rgba(var(--color-primary-rgb, 231,57,84), 0.07); border-left: 3px solid var(--color-primary); } +.topbar-notif-item--unread:hover { background: rgba(var(--color-primary-rgb, 231,57,84), 0.12); } .topbar-notif-dot { width: 7px; height: 7px; diff --git a/xxxthegame/src/main/resources/static/games/bdsm/bdsm.html b/xxxthegame/src/main/resources/static/games/bdsm/bdsm.html deleted file mode 100644 index 72717e6..0000000 --- a/xxxthegame/src/main/resources/static/games/bdsm/bdsm.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - BDSM Game – xXx Sphere - - - - - diff --git a/xxxthegame/src/main/resources/static/games/bdsm/bdsmtasks.html b/xxxthegame/src/main/resources/static/games/bdsm/bdsmtasks.html deleted file mode 100644 index 72717e6..0000000 --- a/xxxthegame/src/main/resources/static/games/bdsm/bdsmtasks.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - BDSM Game – xXx Sphere - - - - - diff --git a/xxxthegame/src/main/resources/static/games/bdsm/bdsmtoys.html b/xxxthegame/src/main/resources/static/games/bdsm/bdsmtoys.html deleted file mode 100644 index 72717e6..0000000 --- a/xxxthegame/src/main/resources/static/games/bdsm/bdsmtoys.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - BDSM Game – xXx Sphere - - - - - diff --git a/xxxthegame/src/main/resources/static/games/bdsm/bdsmwarten.html b/xxxthegame/src/main/resources/static/games/bdsm/bdsmwarten.html deleted file mode 100644 index 72717e6..0000000 --- a/xxxthegame/src/main/resources/static/games/bdsm/bdsmwarten.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - BDSM Game – xXx Sphere - - - - - diff --git a/xxxthegame/src/main/resources/static/games/chastity/joinlock.html b/xxxthegame/src/main/resources/static/games/chastity/joinlock.html index 6f67629..85a600b 100644 --- a/xxxthegame/src/main/resources/static/games/chastity/joinlock.html +++ b/xxxthegame/src/main/resources/static/games/chastity/joinlock.html @@ -104,7 +104,7 @@
⚠️

Einladung nicht gefunden

Diese Einladung existiert nicht oder wurde bereits bearbeitet.

- Zu meinen Einladungen + Zu meinen Einladungen
@@ -117,7 +117,7 @@

Einladung abgelehnt

Du hast die Einladung abgelehnt. Der Keyholder wurde benachrichtigt.

- Zu meinen Einladungen + Zu meinen Einladungen
+
@@ -734,7 +744,7 @@ ${renderSubSection('Aufgaben', sortByLevelThenName(g.aufgaben || []), 'aufgabe', renderAufgabe, g.gruppenId, type)} ${IS_BDSM_MODE ? renderSubSection('Strafen', sortByLevelThenName(g.strafen || []), 'strafe', renderStrafe, g.gruppenId, type) : ''} ${IS_BDSM_MODE ? renderSubSection('Zeitstrafen',sortByName(g.sperren || []), 'zeitstrafe',renderZeitstrafe, g.gruppenId, type) : ''} - ${renderSubSection('Finisher', sortByName(g.finisher || []), 'finisher', renderFinisher, g.gruppenId, type)} + ${renderSubSection('Finisher', sortByGeschlecht(g.finisher || []), 'finisher', renderFinisher, g.gruppenId, type)}
`; }).join(''); @@ -945,6 +955,14 @@ return (a.kurzText || '').localeCompare(b.kurzText || '', 'de'); }); } + const GESCHLECHT_ORDER = { WEIBLICH: 0, DIVERS: 1, MAENNLICH: 2 }; + function sortByGeschlecht(items) { + return items.slice().sort((a, b) => { + const ga = GESCHLECHT_ORDER[a.geschlecht] ?? 99, gb = GESCHLECHT_ORDER[b.geschlecht] ?? 99; + if (ga !== gb) return ga - gb; + return (a.kurzText || '').localeCompare(b.kurzText || '', 'de'); + }); + } function sortByName(items) { return items.slice().sort((a, b) => (a.kurzText || '').localeCompare(b.kurzText || '', 'de')); } @@ -1429,12 +1447,120 @@ document.getElementById('iKurzText').focus(); } - function closeItemModal() { itemModal.classList.remove('open'); closeToySearch(); document.getElementById('iPlaceholderHint').style.display = 'none'; } + function closeItemModal() { itemModal.classList.remove('open'); closeToySearch(); document.getElementById('iPlaceholderHint').style.display = 'none'; document.getElementById('iTextAC').style.display = 'none'; } function togglePlaceholderHint() { const el = document.getElementById('iPlaceholderHint'); el.style.display = el.style.display === 'none' ? 'block' : 'none'; } + (function() { + const STATIC = ['{AKTIV}', '{PASSIV}']; + const ta = document.getElementById('iText'); + const ac = document.getElementById('iTextAC'); + let _allToys = []; + let _items = []; + let _wordStart = 0; + let activeIdx = -1; + + function currentWord() { + const pos = ta.selectionStart; + let start = pos; + while (start > 0 && !/\s/.test(ta.value[start - 1])) start--; + return { word: ta.value.slice(start, pos).toLowerCase(), start }; + } + function buildItems(filter) { + const f = filter || ''; + _items = STATIC.filter(s => !f || s.toLowerCase().includes(f)).map(s => ({ label: s, insert: s })); + const toys = _allToys.filter(t => !f || t.name.toLowerCase().includes(f)); + if (toys.length) { + _items.push({ separator: true, label: 'Toys' }); + toys.forEach(t => _items.push({ label: t.name, insert: t.name, toyId: t.toyId })); + } + } + function selectables() { return _items.map((it, i) => it.separator ? null : i).filter(i => i !== null); } + function renderItems() { + ac.innerHTML = ''; + activeIdx = -1; + _items.forEach((item, i) => { + if (item.separator) { + const sep = document.createElement('div'); + sep.className = 'ac-separator'; + sep.textContent = item.label; + ac.appendChild(sep); + } else { + const div = document.createElement('div'); + div.className = 'ac-item'; + div.dataset.idx = String(i); + div.textContent = item.label; + div.addEventListener('mousedown', e => { e.preventDefault(); doInsert(item); }); + div.addEventListener('mouseover', () => setActive(i)); + ac.appendChild(div); + } + }); + const s = selectables(); + if (s.length) { setActive(s[0]); ac.style.display = 'block'; } else hideAC(); + } + function showAC(toys) { + _allToys = toys || []; + const { word, start } = currentWord(); + _wordStart = start; + buildItems(word); + const rect = ta.getBoundingClientRect(); + ac.style.left = rect.left + 'px'; + ac.style.top = (rect.bottom + 4) + 'px'; + renderItems(); + } + function hideAC() { ac.style.display = 'none'; activeIdx = -1; } + function setActive(i) { + activeIdx = i; + let activeEl = null; + ac.querySelectorAll('.ac-item').forEach(el => { + const on = parseInt(el.dataset.idx) === i; + el.classList.toggle('ac-item-active', on); + if (on) activeEl = el; + }); + if (activeEl) activeEl.scrollIntoView({ block: 'nearest' }); + } + function doInsert(item) { + const end = ta.selectionStart; + ta.value = ta.value.slice(0, _wordStart) + item.insert + ta.value.slice(end); + ta.selectionStart = ta.selectionEnd = _wordStart + item.insert.length; + ta.focus(); + if (item.toyId) { + const already = (_selectedToys || []).find(t => t.toyId === item.toyId); + if (!already) toggleToyFromSearch(item.toyId); + } + hideAC(); + } + ta.addEventListener('keydown', e => { + if (e.ctrlKey && e.code === 'Space') { + e.preventDefault(); + _loadAvailableToys().then(toys => showAC(toys)).catch(() => showAC([])); + return; + } + if (ac.style.display !== 'block') return; + if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); hideAC(); return; } + const s = selectables(); + if (!s.length) return; + const pos = s.indexOf(activeIdx); + if (e.key === 'ArrowDown') { e.preventDefault(); setActive(s[(pos + 1) % s.length]); } + else if (e.key === 'ArrowUp') { e.preventDefault(); setActive(s[(pos - 1 + s.length) % s.length]); } + else if (e.key === 'Enter' || e.key === 'Tab') { + e.preventDefault(); + const item = _items[activeIdx]; + if (item && !item.separator) doInsert(item); + } + }); + ta.addEventListener('input', () => { + if (ac.style.display !== 'block') return; + const { word, start } = currentWord(); + _wordStart = start; + buildItems(word); + renderItems(); + }); + document.addEventListener('mousedown', e => { if (!ac.contains(e.target) && e.target !== ta) hideAC(); }); + })(); + document.getElementById('itemCancelBtn').addEventListener('click', closeItemModal); itemModal.addEventListener('click', e => { if (e.target === itemModal) closeItemModal(); }); diff --git a/xxxthegame/src/main/resources/static/community/einladungen.html b/xxxthegame/src/main/resources/static/games/common/einladungen.html similarity index 97% rename from xxxthegame/src/main/resources/static/community/einladungen.html rename to xxxthegame/src/main/resources/static/games/common/einladungen.html index b05df54..52b7707 100644 --- a/xxxthegame/src/main/resources/static/community/einladungen.html +++ b/xxxthegame/src/main/resources/static/games/common/einladungen.html @@ -602,6 +602,18 @@ document.getElementById('confirmTitle').textContent = title; document.getElementById('confirmText').textContent = text; document.getElementById('confirmModal').classList.add('open'); + document.querySelector('#confirmModal .confirm-modal-cancel').style.display = ''; + return new Promise(resolve => { + _confirmResolve = resolve; + document.getElementById('confirmOkBtn').onclick = () => { confirmClose(true); }; + }); + } + + function showInfo(title, text) { + document.getElementById('confirmTitle').textContent = title; + document.getElementById('confirmText').textContent = text; + document.getElementById('confirmModal').classList.add('open'); + document.querySelector('#confirmModal .confirm-modal-cancel').style.display = 'none'; return new Promise(resolve => { _confirmResolve = resolve; document.getElementById('confirmOkBtn').onclick = () => { confirmClose(true); }; @@ -885,6 +897,9 @@ removeRecvItem(key); if (mode === 'OWN_DEVICE') { window.location.href = `/games/bdsm/neubdsm.html`; + } else if (mode === 'HOST_DEVICE') { + await showInfo('Einladung angenommen', 'Das Spiel findet am Gerät des Hosts statt. Du wirst zur Startseite weitergeleitet.'); + window.location.href = '/userhome.html'; } } catch (_) { errEl.textContent = 'Fehler beim Speichern der Antwort.'; @@ -933,6 +948,7 @@ if (mode === 'OWN_DEVICE') { window.location.href = '/games/vanilla/neuvanilla.html'; } else if (mode === 'HOST_DEVICE') { + await showInfo('Einladung angenommen', 'Das Spiel findet am Gerät des Hosts statt. Du wirst zur Startseite weitergeleitet.'); window.location.href = '/userhome.html'; } } catch (_) { diff --git a/xxxthegame/src/main/resources/static/games/chastity/toys.html b/xxxthegame/src/main/resources/static/games/common/toys.html similarity index 100% rename from xxxthegame/src/main/resources/static/games/chastity/toys.html rename to xxxthegame/src/main/resources/static/games/common/toys.html diff --git a/xxxthegame/src/main/resources/static/games/vanilla/neuvanilla.html b/xxxthegame/src/main/resources/static/games/vanilla/neuvanilla.html index d5b676e..452fdf1 100644 --- a/xxxthegame/src/main/resources/static/games/vanilla/neuvanilla.html +++ b/xxxthegame/src/main/resources/static/games/vanilla/neuvanilla.html @@ -190,7 +190,7 @@

- Gruppen verwalten: Aufgaben-Verwaltung (Vanilla) + Gruppen verwalten: Aufgaben-Verwaltung (Vanilla)

@@ -566,11 +566,14 @@ const ul = document.getElementById(containerId); const section = ul.closest('[id^="section"]'); const selectAllWrap = section?.querySelector('.select-all-label'); - if (!gruppen.length) { + const filtered = gruppen.filter(g => + (g.aufgaben || []).length > 0 || (g.finisher || []).length > 0 + ); + if (!filtered.length) { ul.innerHTML = '
  • Keine Gruppen vorhanden.
  • '; if (selectAllWrap) selectAllWrap.style.visibility = 'hidden'; return; } - ul.innerHTML = gruppen.map(g => { + ul.innerHTML = filtered.map(g => { const checked = savedGruppen.has(g.gruppenId); return `
  • - +
    @@ -275,17 +275,17 @@ try { const res = await fetch('/notifications'); if (!res.ok) { body.innerHTML = '
    Keine Benachrichtigungen.
    '; return; } - const notifs = await res.json(); - if (!notifs.length) { body.innerHTML = '
    Keine neuen Benachrichtigungen.
    '; return; } + const unread = (await res.json()).filter(n => !n.read); + if (!unread.length) { body.innerHTML = '
    Keine neuen Benachrichtigungen.
    '; return; } body.innerHTML = ''; - notifs.forEach(n => { + unread.forEach(n => { const el = document.createElement('div'); const tag = n.targetUrl ? 'a' : 'div'; const href = n.targetUrl ? `href="${esc(n.targetUrl)}"` : ''; const av = n.senderAvatar ? `` : `${IC('PROFILE')}`; - el.innerHTML = `<${tag} ${href} class="topbar-panel-item topbar-notif-item"> + el.innerHTML = `<${tag} ${href} class="topbar-panel-item topbar-notif-item${n.read ? '' : ' topbar-notif-item--unread'}"> ${av}
    ${esc(n.text)}
    @@ -323,18 +323,21 @@ if (!body) return; body.innerHTML = '
    Wird geladen…
    '; try { - const [lr, kr, br] = await Promise.all([ + const [lr, kr, br, vr] = await Promise.all([ fetch('/lockee/invitations/mine'), fetch('/keyholder/invitations/mine'), - fetch('/bdsm/einladung/pending') + fetch('/bdsm/einladung/pending'), + fetch('/vanilla/einladung/pending') ]); - const lockee = lr.ok ? await lr.json() : []; - const kh = kr.ok ? await kr.json() : []; - const bdsm = br.ok ? await br.json() : []; + const lockee = lr.ok ? await lr.json() : []; + const kh = kr.ok ? await kr.json() : []; + const bdsm = br.ok ? await br.json() : []; + const vanilla = vr.ok ? await vr.json() : []; const all = [ ...lockee.map(i => ({ ...i, _type: 'lockee' })), ...kh.map(i => ({ ...i, _type: 'keyholder' })), - ...bdsm.map(i => ({ ...i, _type: 'bdsm' })) + ...bdsm.map(i => ({ ...i, _type: 'bdsm' })), + ...vanilla.map(i => ({ ...i, _type: 'vanilla' })) ]; if (!all.length) { body.innerHTML = '
    Keine offenen Einladungen.
    '; return; } body.innerHTML = ''; @@ -343,66 +346,35 @@ } function buildInvCard(inv) { - let typeIcon, typeName, line, declineUrl, declineMethod = 'DELETE', declineBody = null, acceptHtml; + let typeIcon, typeName, line; if (inv._type === 'lockee') { typeIcon = IC('LOCK'); typeName = 'Lockee-Einladung'; line = inv.lockName || 'Lock'; - declineUrl = '/lockee/invitation/' + encodeURIComponent(inv.token); - acceptHtml = `Details`; } else if (inv._type === 'keyholder') { typeIcon = IC('KEY'); typeName = 'Keyholder-Einladung'; line = inv.lockName || 'Lock'; - declineUrl = '/keyholder/invitations/mine/' + encodeURIComponent(inv.token); - acceptHtml = `Annehmen`; + } else if (inv._type === 'vanilla') { + typeIcon = IC('INVITATIONS'); typeName = 'Vanilla Game'; line = inv.inviterName || 'Einladung'; } else { typeIcon = IC('BDSM'); typeName = 'BDSM Game'; line = inv.senderName || 'Einladung'; - const id = inv.id || inv.einladungId || ''; - declineUrl = '/bdsm/einladung/' + encodeURIComponent(id) + '/antwort'; - declineMethod = 'PUT'; - declineBody = JSON.stringify({ annahme: false }); - acceptHtml = `Details`; } - const senderPic = inv.senderAvatar || inv.lockOwnerAvatar; + const senderPic = inv.senderAvatar || inv.lockOwnerAvatar || inv.inviterAvatar; const av = senderPic ? `` : `${IC('PROFILE')}`; const div = document.createElement('div'); div.className = 'topbar-panel-item topbar-inv-card'; + div.style.cursor = 'pointer'; div.innerHTML = `${av}
    ${typeIcon} ${typeName}
    ${esc(line)}
    -
    -
    - - ${acceptHtml}
    `; + div.addEventListener('click', () => { window.location.href = '/games/common/einladungen.html'; }); return div; } - window.__topbarDecline = async function (btn) { - btn.disabled = true; - const url = btn.dataset.url; - const method = btn.dataset.method; - const body = btn.dataset.body || null; - try { - const opts = { method }; - if (body) { opts.headers = { 'Content-Type': 'application/json' }; opts.body = body; } - const res = await fetch(url, opts); - if (res.ok || res.status === 204) { - const card = btn.closest('.topbar-inv-card'); - if (card) card.remove(); - const remaining = document.getElementById('topbarInvBody')?.querySelectorAll('.topbar-inv-card').length || 0; - setTopbarBadge('inv', remaining); - } else { btn.disabled = false; } - } catch (e) { btn.disabled = false; } - }; - // ── Badge-Verwaltung ── function setTopbarBadge(type, count) { const map = { msg: 'topbarMsgBadge', notif: 'topbarNotifBadge', inv: 'topbarInvBadge' }; @@ -415,9 +387,7 @@ // Für social-sidebar.js zugänglich window.__topbarSetBadge = setTopbarBadge; - function loadInitialBadges() { - fetch('/social/messages/unread/count').then(r => r.ok ? r.json() : 0).then(n => setTopbarBadge('msg', n)).catch(() => {}); - fetch('/notifications/unread/count').then(r => r.ok ? r.json() : 0).then(n => setTopbarBadge('notif', n)).catch(() => {}); + function reloadInvBadge() { Promise.all([ fetch('/lockee/invitations/mine/count').then(r => r.ok ? r.json() : 0).catch(() => 0), fetch('/keyholder/invitations/mine/count').then(r => r.ok ? r.json() : 0).catch(() => 0), @@ -425,4 +395,11 @@ fetch('/vanilla/einladung/pending/count').then(r => r.ok ? r.json() : 0).catch(() => 0) ]).then(([l, k, b, v]) => setTopbarBadge('inv', l + k + b + v)).catch(() => {}); } + window.__topbarReloadInvBadge = reloadInvBadge; + + function loadInitialBadges() { + fetch('/social/messages/unread/count').then(r => r.ok ? r.json() : 0).then(n => setTopbarBadge('msg', n)).catch(() => {}); + fetch('/notifications/unread/count').then(r => r.ok ? r.json() : 0).then(n => setTopbarBadge('notif', n)).catch(() => {}); + reloadInvBadge(); + } })();