Weiter an der Konfig Maske für das BDSM Game gearbeitet, Refactoring der Controller Klassen
This commit is contained in:
@@ -25,7 +25,8 @@
|
|||||||
"Bash(for f:*)",
|
"Bash(for f:*)",
|
||||||
"Bash(ls:*)",
|
"Bash(ls:*)",
|
||||||
"Bash(./gradlew compileJava)",
|
"Bash(./gradlew compileJava)",
|
||||||
"Bash(./gradlew build:*)"
|
"Bash(./gradlew build:*)",
|
||||||
|
"Bash(find /home/mario/Workspaces/xxx-thegame -type f \\\\\\(-name *bdsm* -o -name *BDSM* \\\\\\))"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#Thu Mar 19 23:00:53 CET 2026
|
#Fri Mar 20 15:47:06 CET 2026
|
||||||
display=\:0
|
display=\:0
|
||||||
host=Mario-Linux
|
host=Mario-Linux
|
||||||
process-id=50461
|
process-id=112524
|
||||||
user=mario
|
user=mario
|
||||||
|
|||||||
613
.metadata/.log
613
.metadata/.log
@@ -3416,3 +3416,616 @@ java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plu
|
|||||||
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822)
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822)
|
||||||
at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508)
|
at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508)
|
||||||
at java.base/java.lang.Thread.run(Thread.java:1583)
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
|
||||||
|
!ENTRY org.springframework.tooling.boot.ls 1 0 2026-03-20 00:43:19.865
|
||||||
|
!MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.jdt.core 4 4 2026-03-20 00:43:20.572
|
||||||
|
!MESSAGE Failed to save JDT index: Index for /xxxthegame
|
||||||
|
!STACK 0
|
||||||
|
java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden)
|
||||||
|
at java.base/java.io.FileInputStream.open0(Native Method)
|
||||||
|
at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
|
||||||
|
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:152)
|
||||||
|
at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83)
|
||||||
|
at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633)
|
||||||
|
at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536)
|
||||||
|
at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822)
|
||||||
|
at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508)
|
||||||
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
!SESSION 2026-03-20 07:44:39.589 -----------------------------------------------
|
||||||
|
eclipse.buildId=4.39.0.20260305-0817
|
||||||
|
java.version=21.0.10
|
||||||
|
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 -product org.eclipse.epp.package.java.product
|
||||||
|
|
||||||
|
!ENTRY ch.qos.logback.classic 1 0 2026-03-20 07:44:40.568
|
||||||
|
!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized.
|
||||||
|
|
||||||
|
!ENTRY ch.qos.logback.classic 1 0 2026-03-20 07:44:42.780
|
||||||
|
!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-20 07:44:42.926
|
||||||
|
!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-20 07:44:42.926
|
||||||
|
!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-20 07:44:43.066
|
||||||
|
!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-20 07:44:43.066
|
||||||
|
!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.workbench 4 0 2026-03-20 07:44:54.556
|
||||||
|
!MESSAGE Dynamic menu contribution 'DynamicContributionItems(id=org.eclipse.terminal.connector.local.LocalLauncherDynamicContributionItems, visible=true)' threw an unexpected exception
|
||||||
|
!STACK 0
|
||||||
|
org.eclipse.swt.SWTException: i/o error (java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git\git-for-windows.ico (Datei oder Verzeichnis nicht gefunden))
|
||||||
|
at org.eclipse.swt.SWT.error(SWT.java:4950)
|
||||||
|
at org.eclipse.swt.SWT.error(SWT.java:4865)
|
||||||
|
at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:207)
|
||||||
|
at org.eclipse.swt.graphics.ImageLoader.load(ImageLoader.java:198)
|
||||||
|
at org.eclipse.terminal.view.ui.internal.local.showin.ExternalExecutablesUtils.loadImage(ExternalExecutablesUtils.java:38)
|
||||||
|
at org.eclipse.terminal.view.ui.internal.local.showin.DynamicContributionItems.getContributionItems(DynamicContributionItems.java:76)
|
||||||
|
at org.eclipse.ui.actions.CompoundContributionItem.getContributionItemsToFill(CompoundContributionItem.java:83)
|
||||||
|
at org.eclipse.ui.actions.CompoundContributionItem.fill(CompoundContributionItem.java:57)
|
||||||
|
at org.eclipse.ui.internal.menus.DynamicMenuContributionItem.fill(DynamicMenuContributionItem.java:194)
|
||||||
|
at org.eclipse.jface.action.MenuManager.doItemFill(MenuManager.java:727)
|
||||||
|
at org.eclipse.jface.action.MenuManager.update(MenuManager.java:804)
|
||||||
|
at org.eclipse.jface.action.MenuManager.update(MenuManager.java:671)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.scheduleManagerUpdate(MenuManagerRenderer.java:1149)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.subscribeUIElementTopicVisible(MenuManagerRenderer.java:211)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
|
||||||
|
at org.eclipse.e4.core.internal.di.MethodRequestor.execute(MethodRequestor.java:58)
|
||||||
|
at org.eclipse.swt.widgets.Synchronizer.syncExec(Synchronizer.java:183)
|
||||||
|
at org.eclipse.ui.internal.UISynchronizer.syncExec(UISynchronizer.java:136)
|
||||||
|
at org.eclipse.swt.widgets.Display.syncExec(Display.java:5950)
|
||||||
|
at org.eclipse.e4.ui.workbench.swt.DisplayUISynchronize.syncExec(DisplayUISynchronize.java:34)
|
||||||
|
at org.eclipse.e4.ui.internal.di.UIEventObjectSupplier$UIEventHandler.handleEvent(UIEventObjectSupplier.java:65)
|
||||||
|
at org.eclipse.equinox.internal.event.EventHandlerWrapper.handleEvent(EventHandlerWrapper.java:206)
|
||||||
|
at org.eclipse.equinox.internal.event.EventHandlerTracker.dispatchEvent(EventHandlerTracker.java:201)
|
||||||
|
at org.eclipse.equinox.internal.event.EventHandlerTracker.dispatchEvent(EventHandlerTracker.java:1)
|
||||||
|
at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:230)
|
||||||
|
at org.eclipse.osgi.framework.eventmgr.ListenerQueue.dispatchEventSynchronous(ListenerQueue.java:151)
|
||||||
|
at org.eclipse.equinox.internal.event.EventAdminImpl.dispatchEvent(EventAdminImpl.java:132)
|
||||||
|
at org.eclipse.equinox.internal.event.EventAdminImpl.sendEvent(EventAdminImpl.java:73)
|
||||||
|
at org.eclipse.equinox.internal.event.EventComponent.sendEvent(EventComponent.java:48)
|
||||||
|
at org.eclipse.e4.ui.services.internal.events.EventBroker.send(EventBroker.java:55)
|
||||||
|
at org.eclipse.e4.ui.internal.workbench.UIEventPublisher.notifyChanged(UIEventPublisher.java:61)
|
||||||
|
at org.eclipse.emf.common.notify.impl.BasicNotifierImpl.eNotify(BasicNotifierImpl.java:424)
|
||||||
|
at org.eclipse.e4.ui.model.application.ui.impl.UIElementImpl.setVisible(UIElementImpl.java:365)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.ContributionRecord.updateVisibility(ContributionRecord.java:110)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRendererFilter.updateElementVisibility(MenuManagerRendererFilter.java:169)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRendererFilter.updateElementVisibility(MenuManagerRendererFilter.java:179)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerShowProcessor.showMenu(MenuManagerShowProcessor.java:243)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerShowProcessor.menuAboutToHide(MenuManagerShowProcessor.java:111)
|
||||||
|
at org.eclipse.jface.internal.MenuManagerEventHelper.showEventPostHelper(MenuManagerEventHelper.java:89)
|
||||||
|
at org.eclipse.jface.action.MenuManager.handleAboutToShow(MenuManager.java:467)
|
||||||
|
at org.eclipse.jface.action.MenuManager$2.menuShown(MenuManager.java:493)
|
||||||
|
at org.eclipse.swt.widgets.TypedListener.handleEvent(TypedListener.java:297)
|
||||||
|
at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:91)
|
||||||
|
at org.eclipse.swt.widgets.Display.sendEvent(Display.java:5845)
|
||||||
|
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1656)
|
||||||
|
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1682)
|
||||||
|
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1661)
|
||||||
|
at org.eclipse.swt.widgets.Menu._setVisible(Menu.java:290)
|
||||||
|
at org.eclipse.swt.widgets.Display.runPopups(Display.java:5102)
|
||||||
|
at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:4488)
|
||||||
|
at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine$5.run(PartRenderingEngine.java:1160)
|
||||||
|
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339)
|
||||||
|
at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine.run(PartRenderingEngine.java:1051)
|
||||||
|
at org.eclipse.e4.ui.internal.workbench.E4Workbench.createAndRunUI(E4Workbench.java:153)
|
||||||
|
at org.eclipse.ui.internal.Workbench.lambda$3(Workbench.java:684)
|
||||||
|
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339)
|
||||||
|
at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:583)
|
||||||
|
at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:173)
|
||||||
|
at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:185)
|
||||||
|
at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:219)
|
||||||
|
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:149)
|
||||||
|
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:115)
|
||||||
|
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:467)
|
||||||
|
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:298)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
|
||||||
|
at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:615)
|
||||||
|
at org.eclipse.equinox.launcher.Main.basicRun(Main.java:563)
|
||||||
|
at org.eclipse.equinox.launcher.Main.run(Main.java:1415)
|
||||||
|
at org.eclipse.equinox.launcher.Main.main(Main.java:1387)
|
||||||
|
Caused by: java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git\git-for-windows.ico (Datei oder Verzeichnis nicht gefunden)
|
||||||
|
at java.base/java.io.FileInputStream.open0(Native Method)
|
||||||
|
at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
|
||||||
|
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:152)
|
||||||
|
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:106)
|
||||||
|
at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:204)
|
||||||
|
... 68 more
|
||||||
|
!SESSION 2026-03-20 10:34:12.527 -----------------------------------------------
|
||||||
|
eclipse.buildId=4.39.0.20260305-0817
|
||||||
|
java.version=21.0.10
|
||||||
|
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 -product org.eclipse.epp.package.java.product
|
||||||
|
|
||||||
|
!ENTRY ch.qos.logback.classic 1 0 2026-03-20 10:34:13.217
|
||||||
|
!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized.
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.core.resources 2 10035 2026-03-20 10:34:15.534
|
||||||
|
!MESSAGE The workspace exited with unsaved changes in the previous session; refreshing workspace to recover changes.
|
||||||
|
|
||||||
|
!ENTRY ch.qos.logback.classic 1 0 2026-03-20 10:34:15.774
|
||||||
|
!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-20 10:34:15.901
|
||||||
|
!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-20 10:34:15.901
|
||||||
|
!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-20 10:34:16.018
|
||||||
|
!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-20 10:34:16.018
|
||||||
|
!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.workbench 4 0 2026-03-20 11:05:16.162
|
||||||
|
!MESSAGE Dynamic menu contribution 'DynamicContributionItems(id=org.eclipse.terminal.connector.local.LocalLauncherDynamicContributionItems, visible=true)' threw an unexpected exception
|
||||||
|
!STACK 0
|
||||||
|
org.eclipse.swt.SWTException: i/o error (java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git\git-for-windows.ico (Datei oder Verzeichnis nicht gefunden))
|
||||||
|
at org.eclipse.swt.SWT.error(SWT.java:4950)
|
||||||
|
at org.eclipse.swt.SWT.error(SWT.java:4865)
|
||||||
|
at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:207)
|
||||||
|
at org.eclipse.swt.graphics.ImageLoader.load(ImageLoader.java:198)
|
||||||
|
at org.eclipse.terminal.view.ui.internal.local.showin.ExternalExecutablesUtils.loadImage(ExternalExecutablesUtils.java:38)
|
||||||
|
at org.eclipse.terminal.view.ui.internal.local.showin.DynamicContributionItems.getContributionItems(DynamicContributionItems.java:76)
|
||||||
|
at org.eclipse.ui.actions.CompoundContributionItem.getContributionItemsToFill(CompoundContributionItem.java:83)
|
||||||
|
at org.eclipse.ui.actions.CompoundContributionItem.fill(CompoundContributionItem.java:57)
|
||||||
|
at org.eclipse.ui.internal.menus.DynamicMenuContributionItem.fill(DynamicMenuContributionItem.java:194)
|
||||||
|
at org.eclipse.jface.action.MenuManager.doItemFill(MenuManager.java:727)
|
||||||
|
at org.eclipse.jface.action.MenuManager.update(MenuManager.java:804)
|
||||||
|
at org.eclipse.jface.action.MenuManager.update(MenuManager.java:671)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.scheduleManagerUpdate(MenuManagerRenderer.java:1149)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.subscribeUIElementTopicVisible(MenuManagerRenderer.java:211)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
|
||||||
|
at org.eclipse.e4.core.internal.di.MethodRequestor.execute(MethodRequestor.java:58)
|
||||||
|
at org.eclipse.swt.widgets.Synchronizer.syncExec(Synchronizer.java:183)
|
||||||
|
at org.eclipse.ui.internal.UISynchronizer.syncExec(UISynchronizer.java:136)
|
||||||
|
at org.eclipse.swt.widgets.Display.syncExec(Display.java:5950)
|
||||||
|
at org.eclipse.e4.ui.workbench.swt.DisplayUISynchronize.syncExec(DisplayUISynchronize.java:34)
|
||||||
|
at org.eclipse.e4.ui.internal.di.UIEventObjectSupplier$UIEventHandler.handleEvent(UIEventObjectSupplier.java:65)
|
||||||
|
at org.eclipse.equinox.internal.event.EventHandlerWrapper.handleEvent(EventHandlerWrapper.java:206)
|
||||||
|
at org.eclipse.equinox.internal.event.EventHandlerTracker.dispatchEvent(EventHandlerTracker.java:201)
|
||||||
|
at org.eclipse.equinox.internal.event.EventHandlerTracker.dispatchEvent(EventHandlerTracker.java:1)
|
||||||
|
at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:230)
|
||||||
|
at org.eclipse.osgi.framework.eventmgr.ListenerQueue.dispatchEventSynchronous(ListenerQueue.java:151)
|
||||||
|
at org.eclipse.equinox.internal.event.EventAdminImpl.dispatchEvent(EventAdminImpl.java:132)
|
||||||
|
at org.eclipse.equinox.internal.event.EventAdminImpl.sendEvent(EventAdminImpl.java:73)
|
||||||
|
at org.eclipse.equinox.internal.event.EventComponent.sendEvent(EventComponent.java:48)
|
||||||
|
at org.eclipse.e4.ui.services.internal.events.EventBroker.send(EventBroker.java:55)
|
||||||
|
at org.eclipse.e4.ui.internal.workbench.UIEventPublisher.notifyChanged(UIEventPublisher.java:61)
|
||||||
|
at org.eclipse.emf.common.notify.impl.BasicNotifierImpl.eNotify(BasicNotifierImpl.java:424)
|
||||||
|
at org.eclipse.e4.ui.model.application.ui.impl.UIElementImpl.setVisible(UIElementImpl.java:365)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.ContributionRecord.updateVisibility(ContributionRecord.java:110)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRendererFilter.updateElementVisibility(MenuManagerRendererFilter.java:169)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRendererFilter.updateElementVisibility(MenuManagerRendererFilter.java:179)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerShowProcessor.showMenu(MenuManagerShowProcessor.java:243)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerShowProcessor.menuAboutToHide(MenuManagerShowProcessor.java:111)
|
||||||
|
at org.eclipse.jface.internal.MenuManagerEventHelper.showEventPostHelper(MenuManagerEventHelper.java:89)
|
||||||
|
at org.eclipse.jface.action.MenuManager.handleAboutToShow(MenuManager.java:467)
|
||||||
|
at org.eclipse.jface.action.MenuManager$2.menuShown(MenuManager.java:493)
|
||||||
|
at org.eclipse.swt.widgets.TypedListener.handleEvent(TypedListener.java:297)
|
||||||
|
at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:91)
|
||||||
|
at org.eclipse.swt.widgets.Display.sendEvent(Display.java:5845)
|
||||||
|
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1656)
|
||||||
|
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1682)
|
||||||
|
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1661)
|
||||||
|
at org.eclipse.swt.widgets.Menu._setVisible(Menu.java:290)
|
||||||
|
at org.eclipse.swt.widgets.Display.runPopups(Display.java:5102)
|
||||||
|
at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:4488)
|
||||||
|
at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine$5.run(PartRenderingEngine.java:1160)
|
||||||
|
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339)
|
||||||
|
at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine.run(PartRenderingEngine.java:1051)
|
||||||
|
at org.eclipse.e4.ui.internal.workbench.E4Workbench.createAndRunUI(E4Workbench.java:153)
|
||||||
|
at org.eclipse.ui.internal.Workbench.lambda$3(Workbench.java:684)
|
||||||
|
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339)
|
||||||
|
at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:583)
|
||||||
|
at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:173)
|
||||||
|
at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:185)
|
||||||
|
at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:219)
|
||||||
|
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:149)
|
||||||
|
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:115)
|
||||||
|
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:467)
|
||||||
|
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:298)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
|
||||||
|
at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:615)
|
||||||
|
at org.eclipse.equinox.launcher.Main.basicRun(Main.java:563)
|
||||||
|
at org.eclipse.equinox.launcher.Main.run(Main.java:1415)
|
||||||
|
at org.eclipse.equinox.launcher.Main.main(Main.java:1387)
|
||||||
|
Caused by: java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git\git-for-windows.ico (Datei oder Verzeichnis nicht gefunden)
|
||||||
|
at java.base/java.io.FileInputStream.open0(Native Method)
|
||||||
|
at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
|
||||||
|
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:152)
|
||||||
|
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:106)
|
||||||
|
at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:204)
|
||||||
|
... 68 more
|
||||||
|
!SESSION 2026-03-20 12:07:51.165 -----------------------------------------------
|
||||||
|
eclipse.buildId=4.39.0.20260305-0817
|
||||||
|
java.version=21.0.10
|
||||||
|
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 -product org.eclipse.epp.package.java.product
|
||||||
|
|
||||||
|
!ENTRY ch.qos.logback.classic 1 0 2026-03-20 12:07:51.857
|
||||||
|
!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized.
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.core.resources 2 10035 2026-03-20 12:07:59.028
|
||||||
|
!MESSAGE The workspace exited with unsaved changes in the previous session; refreshing workspace to recover changes.
|
||||||
|
|
||||||
|
!ENTRY ch.qos.logback.classic 1 0 2026-03-20 12:07:59.273
|
||||||
|
!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-20 12:07:59.406
|
||||||
|
!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-20 12:07:59.406
|
||||||
|
!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-20 12:07:59.525
|
||||||
|
!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-20 12:07:59.525
|
||||||
|
!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.workbench 4 0 2026-03-20 12:57:37.530
|
||||||
|
!MESSAGE Dynamic menu contribution 'DynamicContributionItems(id=org.eclipse.terminal.connector.local.LocalLauncherDynamicContributionItems, visible=true)' threw an unexpected exception
|
||||||
|
!STACK 0
|
||||||
|
org.eclipse.swt.SWTException: i/o error (java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git\git-for-windows.ico (Datei oder Verzeichnis nicht gefunden))
|
||||||
|
at org.eclipse.swt.SWT.error(SWT.java:4950)
|
||||||
|
at org.eclipse.swt.SWT.error(SWT.java:4865)
|
||||||
|
at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:207)
|
||||||
|
at org.eclipse.swt.graphics.ImageLoader.load(ImageLoader.java:198)
|
||||||
|
at org.eclipse.terminal.view.ui.internal.local.showin.ExternalExecutablesUtils.loadImage(ExternalExecutablesUtils.java:38)
|
||||||
|
at org.eclipse.terminal.view.ui.internal.local.showin.DynamicContributionItems.getContributionItems(DynamicContributionItems.java:76)
|
||||||
|
at org.eclipse.ui.actions.CompoundContributionItem.getContributionItemsToFill(CompoundContributionItem.java:83)
|
||||||
|
at org.eclipse.ui.actions.CompoundContributionItem.fill(CompoundContributionItem.java:57)
|
||||||
|
at org.eclipse.ui.internal.menus.DynamicMenuContributionItem.fill(DynamicMenuContributionItem.java:194)
|
||||||
|
at org.eclipse.jface.action.MenuManager.doItemFill(MenuManager.java:727)
|
||||||
|
at org.eclipse.jface.action.MenuManager.update(MenuManager.java:804)
|
||||||
|
at org.eclipse.jface.action.MenuManager.update(MenuManager.java:671)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.scheduleManagerUpdate(MenuManagerRenderer.java:1149)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.subscribeUIElementTopicVisible(MenuManagerRenderer.java:211)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
|
||||||
|
at org.eclipse.e4.core.internal.di.MethodRequestor.execute(MethodRequestor.java:58)
|
||||||
|
at org.eclipse.swt.widgets.Synchronizer.syncExec(Synchronizer.java:183)
|
||||||
|
at org.eclipse.ui.internal.UISynchronizer.syncExec(UISynchronizer.java:136)
|
||||||
|
at org.eclipse.swt.widgets.Display.syncExec(Display.java:5950)
|
||||||
|
at org.eclipse.e4.ui.workbench.swt.DisplayUISynchronize.syncExec(DisplayUISynchronize.java:34)
|
||||||
|
at org.eclipse.e4.ui.internal.di.UIEventObjectSupplier$UIEventHandler.handleEvent(UIEventObjectSupplier.java:65)
|
||||||
|
at org.eclipse.equinox.internal.event.EventHandlerWrapper.handleEvent(EventHandlerWrapper.java:206)
|
||||||
|
at org.eclipse.equinox.internal.event.EventHandlerTracker.dispatchEvent(EventHandlerTracker.java:201)
|
||||||
|
at org.eclipse.equinox.internal.event.EventHandlerTracker.dispatchEvent(EventHandlerTracker.java:1)
|
||||||
|
at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:230)
|
||||||
|
at org.eclipse.osgi.framework.eventmgr.ListenerQueue.dispatchEventSynchronous(ListenerQueue.java:151)
|
||||||
|
at org.eclipse.equinox.internal.event.EventAdminImpl.dispatchEvent(EventAdminImpl.java:132)
|
||||||
|
at org.eclipse.equinox.internal.event.EventAdminImpl.sendEvent(EventAdminImpl.java:73)
|
||||||
|
at org.eclipse.equinox.internal.event.EventComponent.sendEvent(EventComponent.java:48)
|
||||||
|
at org.eclipse.e4.ui.services.internal.events.EventBroker.send(EventBroker.java:55)
|
||||||
|
at org.eclipse.e4.ui.internal.workbench.UIEventPublisher.notifyChanged(UIEventPublisher.java:61)
|
||||||
|
at org.eclipse.emf.common.notify.impl.BasicNotifierImpl.eNotify(BasicNotifierImpl.java:424)
|
||||||
|
at org.eclipse.e4.ui.model.application.ui.impl.UIElementImpl.setVisible(UIElementImpl.java:365)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.ContributionRecord.updateVisibility(ContributionRecord.java:110)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRendererFilter.updateElementVisibility(MenuManagerRendererFilter.java:169)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRendererFilter.updateElementVisibility(MenuManagerRendererFilter.java:179)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerShowProcessor.showMenu(MenuManagerShowProcessor.java:243)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerShowProcessor.menuAboutToHide(MenuManagerShowProcessor.java:111)
|
||||||
|
at org.eclipse.jface.internal.MenuManagerEventHelper.showEventPostHelper(MenuManagerEventHelper.java:89)
|
||||||
|
at org.eclipse.jface.action.MenuManager.handleAboutToShow(MenuManager.java:467)
|
||||||
|
at org.eclipse.jface.action.MenuManager$2.menuShown(MenuManager.java:493)
|
||||||
|
at org.eclipse.swt.widgets.TypedListener.handleEvent(TypedListener.java:297)
|
||||||
|
at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:91)
|
||||||
|
at org.eclipse.swt.widgets.Display.sendEvent(Display.java:5845)
|
||||||
|
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1656)
|
||||||
|
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1682)
|
||||||
|
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1661)
|
||||||
|
at org.eclipse.swt.widgets.Menu._setVisible(Menu.java:290)
|
||||||
|
at org.eclipse.swt.widgets.Display.runPopups(Display.java:5102)
|
||||||
|
at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:4488)
|
||||||
|
at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine$5.run(PartRenderingEngine.java:1160)
|
||||||
|
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339)
|
||||||
|
at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine.run(PartRenderingEngine.java:1051)
|
||||||
|
at org.eclipse.e4.ui.internal.workbench.E4Workbench.createAndRunUI(E4Workbench.java:153)
|
||||||
|
at org.eclipse.ui.internal.Workbench.lambda$3(Workbench.java:684)
|
||||||
|
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339)
|
||||||
|
at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:583)
|
||||||
|
at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:173)
|
||||||
|
at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:185)
|
||||||
|
at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:219)
|
||||||
|
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:149)
|
||||||
|
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:115)
|
||||||
|
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:467)
|
||||||
|
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:298)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
|
||||||
|
at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:615)
|
||||||
|
at org.eclipse.equinox.launcher.Main.basicRun(Main.java:563)
|
||||||
|
at org.eclipse.equinox.launcher.Main.run(Main.java:1415)
|
||||||
|
at org.eclipse.equinox.launcher.Main.main(Main.java:1387)
|
||||||
|
Caused by: java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git\git-for-windows.ico (Datei oder Verzeichnis nicht gefunden)
|
||||||
|
at java.base/java.io.FileInputStream.open0(Native Method)
|
||||||
|
at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
|
||||||
|
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:152)
|
||||||
|
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:106)
|
||||||
|
at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:204)
|
||||||
|
... 68 more
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.ui.workbench 4 0 2026-03-20 13:10:19.604
|
||||||
|
!MESSAGE Dynamic menu contribution 'DynamicContributionItems(id=org.eclipse.terminal.connector.local.LocalLauncherDynamicContributionItems, visible=true)' threw an unexpected exception
|
||||||
|
!STACK 0
|
||||||
|
org.eclipse.swt.SWTException: i/o error (java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git\git-for-windows.ico (Datei oder Verzeichnis nicht gefunden))
|
||||||
|
at org.eclipse.swt.SWT.error(SWT.java:4950)
|
||||||
|
at org.eclipse.swt.SWT.error(SWT.java:4865)
|
||||||
|
at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:207)
|
||||||
|
at org.eclipse.swt.graphics.ImageLoader.load(ImageLoader.java:198)
|
||||||
|
at org.eclipse.terminal.view.ui.internal.local.showin.ExternalExecutablesUtils.loadImage(ExternalExecutablesUtils.java:38)
|
||||||
|
at org.eclipse.terminal.view.ui.internal.local.showin.DynamicContributionItems.getContributionItems(DynamicContributionItems.java:76)
|
||||||
|
at org.eclipse.ui.actions.CompoundContributionItem.getContributionItemsToFill(CompoundContributionItem.java:83)
|
||||||
|
at org.eclipse.ui.actions.CompoundContributionItem.fill(CompoundContributionItem.java:57)
|
||||||
|
at org.eclipse.ui.internal.menus.DynamicMenuContributionItem.fill(DynamicMenuContributionItem.java:194)
|
||||||
|
at org.eclipse.jface.action.MenuManager.doItemFill(MenuManager.java:727)
|
||||||
|
at org.eclipse.jface.action.MenuManager.update(MenuManager.java:804)
|
||||||
|
at org.eclipse.jface.action.MenuManager.update(MenuManager.java:671)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.scheduleManagerUpdate(MenuManagerRenderer.java:1149)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.subscribeUIElementTopicVisible(MenuManagerRenderer.java:211)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
|
||||||
|
at org.eclipse.e4.core.internal.di.MethodRequestor.execute(MethodRequestor.java:58)
|
||||||
|
at org.eclipse.swt.widgets.Synchronizer.syncExec(Synchronizer.java:183)
|
||||||
|
at org.eclipse.ui.internal.UISynchronizer.syncExec(UISynchronizer.java:136)
|
||||||
|
at org.eclipse.swt.widgets.Display.syncExec(Display.java:5950)
|
||||||
|
at org.eclipse.e4.ui.workbench.swt.DisplayUISynchronize.syncExec(DisplayUISynchronize.java:34)
|
||||||
|
at org.eclipse.e4.ui.internal.di.UIEventObjectSupplier$UIEventHandler.handleEvent(UIEventObjectSupplier.java:65)
|
||||||
|
at org.eclipse.equinox.internal.event.EventHandlerWrapper.handleEvent(EventHandlerWrapper.java:206)
|
||||||
|
at org.eclipse.equinox.internal.event.EventHandlerTracker.dispatchEvent(EventHandlerTracker.java:201)
|
||||||
|
at org.eclipse.equinox.internal.event.EventHandlerTracker.dispatchEvent(EventHandlerTracker.java:1)
|
||||||
|
at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:230)
|
||||||
|
at org.eclipse.osgi.framework.eventmgr.ListenerQueue.dispatchEventSynchronous(ListenerQueue.java:151)
|
||||||
|
at org.eclipse.equinox.internal.event.EventAdminImpl.dispatchEvent(EventAdminImpl.java:132)
|
||||||
|
at org.eclipse.equinox.internal.event.EventAdminImpl.sendEvent(EventAdminImpl.java:73)
|
||||||
|
at org.eclipse.equinox.internal.event.EventComponent.sendEvent(EventComponent.java:48)
|
||||||
|
at org.eclipse.e4.ui.services.internal.events.EventBroker.send(EventBroker.java:55)
|
||||||
|
at org.eclipse.e4.ui.internal.workbench.UIEventPublisher.notifyChanged(UIEventPublisher.java:61)
|
||||||
|
at org.eclipse.emf.common.notify.impl.BasicNotifierImpl.eNotify(BasicNotifierImpl.java:424)
|
||||||
|
at org.eclipse.e4.ui.model.application.ui.impl.UIElementImpl.setVisible(UIElementImpl.java:365)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.ContributionRecord.updateVisibility(ContributionRecord.java:110)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRendererFilter.updateElementVisibility(MenuManagerRendererFilter.java:169)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRendererFilter.updateElementVisibility(MenuManagerRendererFilter.java:179)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerShowProcessor.showMenu(MenuManagerShowProcessor.java:243)
|
||||||
|
at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerShowProcessor.menuAboutToHide(MenuManagerShowProcessor.java:111)
|
||||||
|
at org.eclipse.jface.internal.MenuManagerEventHelper.showEventPostHelper(MenuManagerEventHelper.java:89)
|
||||||
|
at org.eclipse.jface.action.MenuManager.handleAboutToShow(MenuManager.java:467)
|
||||||
|
at org.eclipse.jface.action.MenuManager$2.menuShown(MenuManager.java:493)
|
||||||
|
at org.eclipse.swt.widgets.TypedListener.handleEvent(TypedListener.java:297)
|
||||||
|
at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:91)
|
||||||
|
at org.eclipse.swt.widgets.Display.sendEvent(Display.java:5845)
|
||||||
|
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1656)
|
||||||
|
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1682)
|
||||||
|
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1661)
|
||||||
|
at org.eclipse.swt.widgets.Menu._setVisible(Menu.java:290)
|
||||||
|
at org.eclipse.swt.widgets.Display.runPopups(Display.java:5102)
|
||||||
|
at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:4488)
|
||||||
|
at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine$5.run(PartRenderingEngine.java:1160)
|
||||||
|
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339)
|
||||||
|
at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine.run(PartRenderingEngine.java:1051)
|
||||||
|
at org.eclipse.e4.ui.internal.workbench.E4Workbench.createAndRunUI(E4Workbench.java:153)
|
||||||
|
at org.eclipse.ui.internal.Workbench.lambda$3(Workbench.java:684)
|
||||||
|
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339)
|
||||||
|
at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:583)
|
||||||
|
at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:173)
|
||||||
|
at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:185)
|
||||||
|
at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:219)
|
||||||
|
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:149)
|
||||||
|
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:115)
|
||||||
|
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:467)
|
||||||
|
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:298)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
|
||||||
|
at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:615)
|
||||||
|
at org.eclipse.equinox.launcher.Main.basicRun(Main.java:563)
|
||||||
|
at org.eclipse.equinox.launcher.Main.run(Main.java:1415)
|
||||||
|
at org.eclipse.equinox.launcher.Main.main(Main.java:1387)
|
||||||
|
Caused by: java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git\git-for-windows.ico (Datei oder Verzeichnis nicht gefunden)
|
||||||
|
at java.base/java.io.FileInputStream.open0(Native Method)
|
||||||
|
at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
|
||||||
|
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:152)
|
||||||
|
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:106)
|
||||||
|
at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:204)
|
||||||
|
... 68 more
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.jdt.core 4 4 2026-03-20 14:51:12.457
|
||||||
|
!MESSAGE Failed to save JDT index: Index for /xxxthegame
|
||||||
|
!STACK 0
|
||||||
|
java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden)
|
||||||
|
at java.base/java.io.FileInputStream.open0(Native Method)
|
||||||
|
at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
|
||||||
|
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:152)
|
||||||
|
at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83)
|
||||||
|
at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633)
|
||||||
|
at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536)
|
||||||
|
at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822)
|
||||||
|
at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508)
|
||||||
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.jdt.core 4 4 2026-03-20 15:37:34.279
|
||||||
|
!MESSAGE Failed to save JDT index: Index for /xxxthegame
|
||||||
|
!STACK 0
|
||||||
|
java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden)
|
||||||
|
at java.base/java.io.FileInputStream.open0(Native Method)
|
||||||
|
at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
|
||||||
|
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:152)
|
||||||
|
at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83)
|
||||||
|
at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633)
|
||||||
|
at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536)
|
||||||
|
at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822)
|
||||||
|
at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508)
|
||||||
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.jdt.core 4 4 2026-03-20 15:37:39.025
|
||||||
|
!MESSAGE Failed to save JDT index: Index for /xxxthegame
|
||||||
|
!STACK 0
|
||||||
|
java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden)
|
||||||
|
at java.base/java.io.FileInputStream.open0(Native Method)
|
||||||
|
at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
|
||||||
|
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:152)
|
||||||
|
at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83)
|
||||||
|
at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633)
|
||||||
|
at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536)
|
||||||
|
at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822)
|
||||||
|
at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508)
|
||||||
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.jdt.core 4 4 2026-03-20 15:37:48.762
|
||||||
|
!MESSAGE Failed to save JDT index: Index for /xxxthegame
|
||||||
|
!STACK 0
|
||||||
|
java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden)
|
||||||
|
at java.base/java.io.FileInputStream.open0(Native Method)
|
||||||
|
at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
|
||||||
|
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:152)
|
||||||
|
at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83)
|
||||||
|
at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633)
|
||||||
|
at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536)
|
||||||
|
at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822)
|
||||||
|
at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508)
|
||||||
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.jdt.core 4 4 2026-03-20 15:38:19.558
|
||||||
|
!MESSAGE Failed to save JDT index: Index for /xxxthegame
|
||||||
|
!STACK 0
|
||||||
|
java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden)
|
||||||
|
at java.base/java.io.FileInputStream.open0(Native Method)
|
||||||
|
at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
|
||||||
|
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:152)
|
||||||
|
at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83)
|
||||||
|
at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633)
|
||||||
|
at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536)
|
||||||
|
at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822)
|
||||||
|
at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508)
|
||||||
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.jdt.core 4 4 2026-03-20 15:38:43.890
|
||||||
|
!MESSAGE Failed to save JDT index: Index for /xxxthegame
|
||||||
|
!STACK 0
|
||||||
|
java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden)
|
||||||
|
at java.base/java.io.FileInputStream.open0(Native Method)
|
||||||
|
at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
|
||||||
|
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:152)
|
||||||
|
at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83)
|
||||||
|
at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633)
|
||||||
|
at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536)
|
||||||
|
at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822)
|
||||||
|
at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508)
|
||||||
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.jdt.core 4 4 2026-03-20 15:38:51.083
|
||||||
|
!MESSAGE Failed to save JDT index: Index for /xxxthegame
|
||||||
|
!STACK 0
|
||||||
|
java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden)
|
||||||
|
at java.base/java.io.FileInputStream.open0(Native Method)
|
||||||
|
at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
|
||||||
|
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:152)
|
||||||
|
at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83)
|
||||||
|
at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633)
|
||||||
|
at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536)
|
||||||
|
at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822)
|
||||||
|
at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508)
|
||||||
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.jface 2 0 2026-03-20 15:42:01.796
|
||||||
|
!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation.
|
||||||
|
!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-20 15:42:01.796
|
||||||
|
!MESSAGE A conflict occurred for CTRL+SHIFT+T:
|
||||||
|
Binding(CTRL+SHIFT+T,
|
||||||
|
ParameterizedCommand(Command(org.eclipse.jdt.ui.navigate.open.type,Open Type,
|
||||||
|
Open a type in a Java editor,
|
||||||
|
Category(org.eclipse.ui.category.navigate,Navigate,null,true),
|
||||||
|
WorkbenchHandlerServiceHandler("org.eclipse.jdt.ui.navigate.open.type"),
|
||||||
|
,,true),null),
|
||||||
|
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||||
|
org.eclipse.ui.contexts.window,,,system)
|
||||||
|
Binding(CTRL+SHIFT+T,
|
||||||
|
ParameterizedCommand(Command(org.eclipse.lsp4e.symbolInWorkspace,Go to Symbol in Workspace,
|
||||||
|
,
|
||||||
|
Category(org.eclipse.lsp4e.category,Language Servers,null,true),
|
||||||
|
WorkbenchHandlerServiceHandler("org.eclipse.lsp4e.symbolInWorkspace"),
|
||||||
|
,,true),null),
|
||||||
|
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||||
|
org.eclipse.ui.contexts.window,,,system)
|
||||||
|
|
||||||
|
!ENTRY org.springframework.tooling.boot.ls 1 0 2026-03-20 15:47:03.456
|
||||||
|
!MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.jdt.core 4 4 2026-03-20 15:47:03.843
|
||||||
|
!MESSAGE Failed to save JDT index: Index for /xxxthegame
|
||||||
|
!STACK 0
|
||||||
|
java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden)
|
||||||
|
at java.base/java.io.FileInputStream.open0(Native Method)
|
||||||
|
at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
|
||||||
|
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:152)
|
||||||
|
at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83)
|
||||||
|
at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633)
|
||||||
|
at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536)
|
||||||
|
at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178)
|
||||||
|
at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822)
|
||||||
|
at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508)
|
||||||
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
!SESSION 2026-03-20 15:47:04.884 -----------------------------------------------
|
||||||
|
eclipse.buildId=4.39.0.20260305-0817
|
||||||
|
java.version=21.0.10
|
||||||
|
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 -product org.eclipse.epp.package.java.product -data file:/home/mario/Workspaces/xxx-thegame/
|
||||||
|
|
||||||
|
!ENTRY ch.qos.logback.classic 1 0 2026-03-20 15:47:05.724
|
||||||
|
!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized.
|
||||||
|
|
||||||
|
!ENTRY ch.qos.logback.classic 1 0 2026-03-20 15:47:06.276
|
||||||
|
!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-20 15:47:06.437
|
||||||
|
!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-20 15:47:06.437
|
||||||
|
!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-20 15:47:06.565
|
||||||
|
!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-20 15:47:06.565
|
||||||
|
!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
|
||||||
|
|
||||||
|
!ENTRY org.springframework.tooling.boot.ls 1 0 2026-03-20 16:01:51.334
|
||||||
|
!MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[ {
|
[ {
|
||||||
"version" : "9.4.1-20260319034812+0000",
|
"version" : "9.5.0-20260320031124+0000",
|
||||||
"buildTime" : "20260319034812+0000",
|
"buildTime" : "20260320031124+0000",
|
||||||
"commitId" : "2d6327017519d23b96af35865dc997fcb544fb40",
|
"commitId" : "97faa73152fb6d4ea37edf6b3f7590dcbce8b952",
|
||||||
"current" : false,
|
"current" : false,
|
||||||
"snapshot" : true,
|
"snapshot" : true,
|
||||||
"nightly" : false,
|
"nightly" : false,
|
||||||
@@ -10,15 +10,15 @@
|
|||||||
"rcFor" : "",
|
"rcFor" : "",
|
||||||
"milestoneFor" : "",
|
"milestoneFor" : "",
|
||||||
"broken" : false,
|
"broken" : false,
|
||||||
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.4.1-20260319034812+0000-bin.zip",
|
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260320031124+0000-bin.zip",
|
||||||
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.4.1-20260319034812+0000-bin.zip.sha256",
|
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260320031124+0000-bin.zip.sha256",
|
||||||
"checksum" : "a1f30c1e81a9e33725a213f158d5044dd305f438e539983f683f58b5860ab65e",
|
"checksum" : "d28982b60bd15c7f3e13032152fc384f30465713b9c439bd3e159ad758461393",
|
||||||
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.4.1-20260319034812+0000-wrapper.jar.sha256",
|
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260320031124+0000-wrapper.jar.sha256",
|
||||||
"wrapperChecksum" : "55243ef57851f12b070ad14f7f5bb8302daceeebc5bce5ece5fa6edb23e1145c"
|
"wrapperChecksum" : "f307680272dffdb8e636f1169adfbf693513005c80aa06e8d381f20390a06e6a"
|
||||||
}, {
|
}, {
|
||||||
"version" : "9.5.0-20260319005705+0000",
|
"version" : "9.6.0-20260319194115+0000",
|
||||||
"buildTime" : "20260319005705+0000",
|
"buildTime" : "20260319194115+0000",
|
||||||
"commitId" : "312894732cc8829c4f69bd292c9b259a1f5bfd8f",
|
"commitId" : "eaac62111b6cbb05984176b52e4be56d5249ebf8",
|
||||||
"current" : false,
|
"current" : false,
|
||||||
"snapshot" : true,
|
"snapshot" : true,
|
||||||
"nightly" : true,
|
"nightly" : true,
|
||||||
@@ -27,11 +27,28 @@
|
|||||||
"rcFor" : "",
|
"rcFor" : "",
|
||||||
"milestoneFor" : "",
|
"milestoneFor" : "",
|
||||||
"broken" : false,
|
"broken" : false,
|
||||||
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260319005705+0000-bin.zip",
|
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260319194115+0000-bin.zip",
|
||||||
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260319005705+0000-bin.zip.sha256",
|
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260319194115+0000-bin.zip.sha256",
|
||||||
"checksum" : "9c1f5565f97acfcbfd7b6e2a0be3eb65f366f8d522b0c82f82839d08fd8d3aaf",
|
"checksum" : "a72c6e0f1a5ecc7d81768c65a5bdcd8f0af37ba5b05c83df4c45d08b8ce79fce",
|
||||||
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260319005705+0000-wrapper.jar.sha256",
|
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260319194115+0000-wrapper.jar.sha256",
|
||||||
"wrapperChecksum" : "7ef3d73bd95c047814d76ec8324f72deefb96593eb9ce87aa06ecdcdaba7ffe8"
|
"wrapperChecksum" : "f307680272dffdb8e636f1169adfbf693513005c80aa06e8d381f20390a06e6a"
|
||||||
|
}, {
|
||||||
|
"version" : "9.4.1",
|
||||||
|
"buildTime" : "20260319084628+0000",
|
||||||
|
"commitId" : "2d6327017519d23b96af35865dc997fcb544fb40",
|
||||||
|
"current" : true,
|
||||||
|
"snapshot" : false,
|
||||||
|
"nightly" : false,
|
||||||
|
"releaseNightly" : false,
|
||||||
|
"activeRc" : false,
|
||||||
|
"rcFor" : "",
|
||||||
|
"milestoneFor" : "",
|
||||||
|
"broken" : false,
|
||||||
|
"downloadUrl" : "https://services.gradle.org/distributions/gradle-9.4.1-bin.zip",
|
||||||
|
"checksumUrl" : "https://services.gradle.org/distributions/gradle-9.4.1-bin.zip.sha256",
|
||||||
|
"checksum" : "2ab2958f2a1e51120c326cad6f385153bb11ee93b3c216c5fccebfdfbb7ec6cb",
|
||||||
|
"wrapperChecksumUrl" : "https://services.gradle.org/distributions/gradle-9.4.1-wrapper.jar.sha256",
|
||||||
|
"wrapperChecksum" : "55243ef57851f12b070ad14f7f5bb8302daceeebc5bce5ece5fa6edb23e1145c"
|
||||||
}, {
|
}, {
|
||||||
"version" : "9.5.0-milestone-7",
|
"version" : "9.5.0-milestone-7",
|
||||||
"buildTime" : "20260315084051+0000",
|
"buildTime" : "20260315084051+0000",
|
||||||
@@ -53,7 +70,7 @@
|
|||||||
"version" : "9.4.0",
|
"version" : "9.4.0",
|
||||||
"buildTime" : "20260304103600+0000",
|
"buildTime" : "20260304103600+0000",
|
||||||
"commitId" : "b631911858264c0b6e4d6603d677ff5218766cee",
|
"commitId" : "b631911858264c0b6e4d6603d677ff5218766cee",
|
||||||
"current" : true,
|
"current" : false,
|
||||||
"snapshot" : false,
|
"snapshot" : false,
|
||||||
"nightly" : false,
|
"nightly" : false,
|
||||||
"releaseNightly" : false,
|
"releaseNightly" : false,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -4,13 +4,14 @@ INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.ec
|
|||||||
176453541.index
|
176453541.index
|
||||||
677104696.index
|
677104696.index
|
||||||
341080888.index
|
341080888.index
|
||||||
774576701.index
|
|
||||||
4134502745.index
|
4134502745.index
|
||||||
|
774576701.index
|
||||||
41199409.index
|
41199409.index
|
||||||
2217896880.index
|
2217896880.index
|
||||||
134995224.index
|
134995224.index
|
||||||
4025319337.index
|
4025319337.index
|
||||||
900586112.index
|
900586112.index
|
||||||
|
9341915.index
|
||||||
2929476459.index
|
2929476459.index
|
||||||
2065500052.index
|
2065500052.index
|
||||||
3051047092.index
|
3051047092.index
|
||||||
@@ -27,8 +28,8 @@ INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.ec
|
|||||||
2032345814.index
|
2032345814.index
|
||||||
3839581777.index
|
3839581777.index
|
||||||
2466743981.index
|
2466743981.index
|
||||||
13999064.index
|
|
||||||
673436610.index
|
673436610.index
|
||||||
|
13999064.index
|
||||||
3972616808.index
|
3972616808.index
|
||||||
1914043487.index
|
1914043487.index
|
||||||
3154281632.index
|
3154281632.index
|
||||||
@@ -44,10 +45,10 @@ INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.ec
|
|||||||
4020783879.index
|
4020783879.index
|
||||||
2900482015.index
|
2900482015.index
|
||||||
3059431983.index
|
3059431983.index
|
||||||
833027591.index
|
|
||||||
13156219.index
|
13156219.index
|
||||||
37241354.index
|
833027591.index
|
||||||
4088356365.index
|
4088356365.index
|
||||||
|
37241354.index
|
||||||
1295630681.index
|
1295630681.index
|
||||||
2701419231.index
|
2701419231.index
|
||||||
3939420913.index
|
3939420913.index
|
||||||
@@ -55,35 +56,35 @@ INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.ec
|
|||||||
1318022262.index
|
1318022262.index
|
||||||
773718761.index
|
773718761.index
|
||||||
2311226047.index
|
2311226047.index
|
||||||
3539841425.index
|
|
||||||
1865797976.index
|
1865797976.index
|
||||||
|
3539841425.index
|
||||||
2455962971.index
|
2455962971.index
|
||||||
836138551.index
|
2576972120.index
|
||||||
2389383899.index
|
2389383899.index
|
||||||
2226615777.index
|
2226615777.index
|
||||||
3515611559.index
|
3515611559.index
|
||||||
3728851734.index
|
|
||||||
2826242951.index
|
|
||||||
2899155238.index
|
2899155238.index
|
||||||
3763224039.index
|
836138551.index
|
||||||
2138052223.index
|
2138052223.index
|
||||||
|
3763224039.index
|
||||||
|
3728851734.index
|
||||||
2236377038.index
|
2236377038.index
|
||||||
3547251881.index
|
3547251881.index
|
||||||
371677185.index
|
|
||||||
2127778675.index
|
|
||||||
2519831052.index
|
|
||||||
1063231598.index
|
|
||||||
2874180664.index
|
|
||||||
2939623059.index
|
|
||||||
2576972120.index
|
|
||||||
2376429633.index
|
|
||||||
2628068441.index
|
|
||||||
1090991043.index
|
|
||||||
1138623861.index
|
1138623861.index
|
||||||
|
2376429633.index
|
||||||
|
2519831052.index
|
||||||
|
371677185.index
|
||||||
|
2874180664.index
|
||||||
|
1090991043.index
|
||||||
|
2826242951.index
|
||||||
|
2127778675.index
|
||||||
|
2628068441.index
|
||||||
|
1063231598.index
|
||||||
|
2939623059.index
|
||||||
1223891870.index
|
1223891870.index
|
||||||
3769604005.index
|
3769604005.index
|
||||||
3158780236.index
|
|
||||||
2237645717.index
|
2237645717.index
|
||||||
|
3158780236.index
|
||||||
2852275968.index
|
2852275968.index
|
||||||
2403041570.index
|
2403041570.index
|
||||||
1704193220.index
|
1704193220.index
|
||||||
@@ -95,12 +96,12 @@ INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.ec
|
|||||||
352173590.index
|
352173590.index
|
||||||
766439048.index
|
766439048.index
|
||||||
3424266581.index
|
3424266581.index
|
||||||
2247053514.index
|
|
||||||
1765772496.index
|
1765772496.index
|
||||||
3514351073.index
|
|
||||||
3892622621.index
|
|
||||||
2494834982.index
|
2494834982.index
|
||||||
1780956574.index
|
3514351073.index
|
||||||
1022297761.index
|
2247053514.index
|
||||||
1938594271.index
|
1938594271.index
|
||||||
|
3892622621.index
|
||||||
|
1022297761.index
|
||||||
|
1780956574.index
|
||||||
1256436118.index
|
1256436118.index
|
||||||
|
|||||||
@@ -2,4 +2,6 @@
|
|||||||
<typeInfoHistroy>
|
<typeInfoHistroy>
|
||||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.aufgaben.controller{AboController.java[AboController" modifiers="1" timestamp="1773400404000"/>
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.aufgaben.controller{AboController.java[AboController" modifiers="1" timestamp="1773400404000"/>
|
||||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.history{GameHistoryEntity.java[GameHistoryEntity" modifiers="1" timestamp="1773860770365"/>
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.history{GameHistoryEntity.java[GameHistoryEntity" modifiers="1" timestamp="1773860770365"/>
|
||||||
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.bdsm.controller{BdsmGameController.java[BdsmGameController" modifiers="1" timestamp="1774017499554"/>
|
||||||
|
<typeInfo handle="=xxxthegame/\/usr\/lib\/jvm\/java-21-openjdk-amd64\/lib\/jrt-fs.jar`java.base=/javadoc_location=/https:\/\/docs.oracle.com\/en\/java\/javase\/21\/docs\/api\/=/<java.util(UUID.class[UUID" modifiers="49" timestamp="1769125611000"/>
|
||||||
</typeInfoHistroy>
|
</typeInfoHistroy>
|
||||||
|
|||||||
@@ -27,3 +27,7 @@
|
|||||||
2026-03-19 18:15:12,369 [Worker-5: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
2026-03-19 18:15:12,369 [Worker-5: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
||||||
2026-03-19 21:54:37,068 [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-19 21:54:37,068 [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-19 23:00:56,635 [Worker-1: 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-19 23:00:56,635 [Worker-1: 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-20 07:44:45,034 [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-20 10:34:17,517 [Worker-5: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
||||||
|
2026-03-20 12:08:01,037 [Worker-1: 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-20 15:47:08,644 [Worker-1: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#Thu Mar 19 23:00:53 CET 2026
|
#Fri Mar 20 15:47:06 CET 2026
|
||||||
org.eclipse.core.runtime=2
|
org.eclipse.core.runtime=2
|
||||||
org.eclipse.platform=4.39.0.v20260226-0420
|
org.eclipse.platform=4.39.0.v20260226-0420
|
||||||
|
|||||||
BIN
bilder/logo_dating.png
Normal file
BIN
bilder/logo_dating.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
bilder/logo_dating_transparent.png
Normal file
BIN
bilder/logo_dating_transparent.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 778 KiB |
@@ -0,0 +1,190 @@
|
|||||||
|
package de.oaa.xxx.aufgaben;
|
||||||
|
|
||||||
|
import de.oaa.xxx.aufgaben.entity.AufgabeEntity;
|
||||||
|
import de.oaa.xxx.aufgaben.entity.AufgabenGruppeEntity;
|
||||||
|
import de.oaa.xxx.aufgaben.entity.FinisherEntity;
|
||||||
|
import de.oaa.xxx.aufgaben.entity.SperreEntity;
|
||||||
|
import de.oaa.xxx.aufgaben.entity.StrafeEntity;
|
||||||
|
import de.oaa.xxx.aufgaben.entity.ToyEntity;
|
||||||
|
import de.oaa.xxx.aufgaben.repository.AufgabeRepository;
|
||||||
|
import de.oaa.xxx.aufgaben.repository.AufgabenGruppeRepository;
|
||||||
|
import de.oaa.xxx.aufgaben.repository.FinisherRepository;
|
||||||
|
import de.oaa.xxx.aufgaben.repository.SperreRepository;
|
||||||
|
import de.oaa.xxx.aufgaben.repository.StrafeRepository;
|
||||||
|
import de.oaa.xxx.aufgaben.repository.ToyRepository;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service für komplexe AufgabenGruppen-Operationen.
|
||||||
|
* Kapselt die Kopier-Logik (Systemgruppe → eigene Gruppe) inkl. Toy-Mapping.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class AufgabenGruppeService {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(AufgabenGruppeService.class);
|
||||||
|
|
||||||
|
private final AufgabenGruppeRepository gruppeRepository;
|
||||||
|
private final AufgabeRepository aufgabeRepository;
|
||||||
|
private final StrafeRepository strafeRepository;
|
||||||
|
private final SperreRepository sperreRepository;
|
||||||
|
private final FinisherRepository finisherRepository;
|
||||||
|
private final ToyRepository toyRepository;
|
||||||
|
|
||||||
|
public AufgabenGruppeService(AufgabenGruppeRepository gruppeRepository,
|
||||||
|
AufgabeRepository aufgabeRepository,
|
||||||
|
StrafeRepository strafeRepository,
|
||||||
|
SperreRepository sperreRepository,
|
||||||
|
FinisherRepository finisherRepository,
|
||||||
|
ToyRepository toyRepository) {
|
||||||
|
this.gruppeRepository = gruppeRepository;
|
||||||
|
this.aufgabeRepository = aufgabeRepository;
|
||||||
|
this.strafeRepository = strafeRepository;
|
||||||
|
this.sperreRepository = sperreRepository;
|
||||||
|
this.finisherRepository = finisherRepository;
|
||||||
|
this.toyRepository = toyRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kopiert eine öffentliche (System-)Gruppe in die eigene Sammlung des Users.
|
||||||
|
*
|
||||||
|
* @param sourceId UUID der Quellgruppe
|
||||||
|
* @param userId UUID des Ziel-Users
|
||||||
|
* @return UUID der neu erstellten Gruppe
|
||||||
|
* @throws IllegalStateException wenn das Gruppen-Limit (10) erreicht ist
|
||||||
|
* @throws IllegalArgumentException wenn die Quellgruppe nicht gefunden oder nicht kopierbar ist
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public UUID copyGruppe(UUID sourceId, UUID userId) {
|
||||||
|
if (gruppeRepository.countByUserId(userId) >= 10) {
|
||||||
|
throw new IllegalStateException("Gruppen-Limit erreicht");
|
||||||
|
}
|
||||||
|
|
||||||
|
AufgabenGruppeEntity source = gruppeRepository.findById(sourceId)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("Gruppe nicht gefunden: " + sourceId));
|
||||||
|
if (source.isPrivateGruppe()) {
|
||||||
|
throw new IllegalArgumentException("Privat-Gruppen können nicht kopiert werden");
|
||||||
|
}
|
||||||
|
if (userId.equals(source.getUserId())) {
|
||||||
|
throw new IllegalArgumentException("Eigene Gruppen können nicht kopiert werden");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toy-Mapping aufbauen: Source-ToyId → Ziel-ToyEntity
|
||||||
|
Set<ToyEntity> allSourceToys = new HashSet<>();
|
||||||
|
source.getAufgaben().forEach(a -> { if (a.getBenoetigteToys() != null) allSourceToys.addAll(a.getBenoetigteToys()); });
|
||||||
|
source.getStrafen().forEach(s -> { if (s.getBenoetigteToys() != null) allSourceToys.addAll(s.getBenoetigteToys()); });
|
||||||
|
source.getSperren().forEach(sp -> { if (sp.getBenoetigteToys() != null) allSourceToys.addAll(sp.getBenoetigteToys()); });
|
||||||
|
source.getFinisher().forEach(f -> { if (f.getBenoetigteToys() != null) allSourceToys.addAll(f.getBenoetigteToys()); });
|
||||||
|
|
||||||
|
Map<UUID, ToyEntity> toyMapping = new HashMap<>();
|
||||||
|
for (ToyEntity sourceToy : allSourceToys) {
|
||||||
|
if (sourceToy.getUserId() == null) {
|
||||||
|
// System-Toy: direkt referenzieren
|
||||||
|
toyMapping.put(sourceToy.getToyId(), sourceToy);
|
||||||
|
} else {
|
||||||
|
// User-Toy: gleichnamiges Toy suchen oder kopieren
|
||||||
|
ToyEntity mapped = toyRepository.findByNameIgnoreCaseAndUserId(sourceToy.getName(), userId)
|
||||||
|
.orElseGet(() -> {
|
||||||
|
ToyEntity tc = new ToyEntity();
|
||||||
|
tc.setToyId(UUID.randomUUID());
|
||||||
|
tc.setName(sourceToy.getName());
|
||||||
|
tc.setBeschreibung(sourceToy.getBeschreibung());
|
||||||
|
tc.setBild(sourceToy.getBild());
|
||||||
|
tc.setUserId(userId);
|
||||||
|
return toyRepository.save(tc);
|
||||||
|
});
|
||||||
|
toyMapping.put(sourceToy.getToyId(), mapped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neue Gruppe anlegen
|
||||||
|
AufgabenGruppeEntity copy = new AufgabenGruppeEntity();
|
||||||
|
copy.setGruppenId(UUID.randomUUID());
|
||||||
|
copy.setName(source.getName());
|
||||||
|
copy.setBeschreibung(source.getBeschreibung());
|
||||||
|
copy.setVon(source.getVon());
|
||||||
|
copy.setBild(source.getBild());
|
||||||
|
copy.setUserId(userId);
|
||||||
|
copy.setPrivateGruppe(true);
|
||||||
|
gruppeRepository.save(copy);
|
||||||
|
|
||||||
|
// Aufgaben kopieren
|
||||||
|
for (AufgabeEntity a : source.getAufgaben()) {
|
||||||
|
AufgabeEntity ac = new AufgabeEntity();
|
||||||
|
ac.setAufgabeId(UUID.randomUUID());
|
||||||
|
ac.setAufgabenGruppe(copy);
|
||||||
|
ac.setKurzText(a.getKurzText());
|
||||||
|
ac.setText(a.getText());
|
||||||
|
ac.setLevel(a.getLevel());
|
||||||
|
ac.setSekundenVon(a.getSekundenVon());
|
||||||
|
ac.setSekundenBis(a.getSekundenBis());
|
||||||
|
ac.setBenoetigtAktiv(a.getBenoetigtAktiv() != null ? new ArrayList<>(a.getBenoetigtAktiv()) : null);
|
||||||
|
ac.setBenoetigtPassiv(a.getBenoetigtPassiv() != null ? new ArrayList<>(a.getBenoetigtPassiv()) : null);
|
||||||
|
ac.setBenoetigteToys(mapToys(a.getBenoetigteToys(), toyMapping));
|
||||||
|
aufgabeRepository.save(ac);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strafen kopieren
|
||||||
|
for (StrafeEntity s : source.getStrafen()) {
|
||||||
|
StrafeEntity sc = new StrafeEntity();
|
||||||
|
sc.setStrafeId(UUID.randomUUID());
|
||||||
|
sc.setAufgabenGruppe(copy);
|
||||||
|
sc.setKurzText(s.getKurzText());
|
||||||
|
sc.setText(s.getText());
|
||||||
|
sc.setLevel(s.getLevel());
|
||||||
|
sc.setSekundenVon(s.getSekundenVon());
|
||||||
|
sc.setSekundenBis(s.getSekundenBis());
|
||||||
|
sc.setBenoetigtAktiv(s.getBenoetigtAktiv() != null ? new ArrayList<>(s.getBenoetigtAktiv()) : null);
|
||||||
|
sc.setBenoetigtPassiv(s.getBenoetigtPassiv() != null ? new ArrayList<>(s.getBenoetigtPassiv()) : null);
|
||||||
|
sc.setBenoetigteToys(mapToys(s.getBenoetigteToys(), toyMapping));
|
||||||
|
strafeRepository.save(sc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sperren kopieren
|
||||||
|
for (SperreEntity sp : source.getSperren()) {
|
||||||
|
SperreEntity spc = new SperreEntity();
|
||||||
|
spc.setSperreId(UUID.randomUUID());
|
||||||
|
spc.setAufgabenGruppe(copy);
|
||||||
|
spc.setKurzText(sp.getKurzText());
|
||||||
|
spc.setText(sp.getText());
|
||||||
|
spc.setReleaseText(sp.getReleaseText());
|
||||||
|
spc.setMinutenVon(sp.getMinutenVon());
|
||||||
|
spc.setMinutenBis(sp.getMinutenBis());
|
||||||
|
spc.setSperreFuer(sp.getSperreFuer() != null ? new ArrayList<>(sp.getSperreFuer()) : null);
|
||||||
|
spc.setBenoetigteToys(mapToys(sp.getBenoetigteToys(), toyMapping));
|
||||||
|
sperreRepository.save(spc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finisher kopieren
|
||||||
|
for (FinisherEntity f : source.getFinisher()) {
|
||||||
|
FinisherEntity fc = new FinisherEntity();
|
||||||
|
fc.setFinisherId(UUID.randomUUID());
|
||||||
|
fc.setAufgabenGruppe(copy);
|
||||||
|
fc.setKurzText(f.getKurzText());
|
||||||
|
fc.setText(f.getText());
|
||||||
|
fc.setGeschlecht(f.getGeschlecht());
|
||||||
|
fc.setBenoetigtAktiv(f.getBenoetigtAktiv() != null ? new ArrayList<>(f.getBenoetigtAktiv()) : null);
|
||||||
|
fc.setBenoetigtPassiv(f.getBenoetigtPassiv() != null ? new ArrayList<>(f.getBenoetigtPassiv()) : null);
|
||||||
|
fc.setBenoetigteToys(mapToys(f.getBenoetigteToys(), toyMapping));
|
||||||
|
finisherRepository.save(fc);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("User {} hat AufgabenGruppe {} kopiert (Quelle: {})", userId, copy.getGruppenId(), sourceId);
|
||||||
|
return copy.getGruppenId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ToyEntity> mapToys(List<ToyEntity> source, Map<UUID, ToyEntity> mapping) {
|
||||||
|
if (source == null || source.isEmpty()) return new ArrayList<>();
|
||||||
|
return source.stream().map(t -> mapping.getOrDefault(t.getToyId(), t)).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,9 @@
|
|||||||
package de.oaa.xxx.aufgaben.controller;
|
package de.oaa.xxx.aufgaben.controller;
|
||||||
|
|
||||||
import de.oaa.xxx.aufgaben.AufgabenGruppe;
|
import java.security.Principal;
|
||||||
import de.oaa.xxx.aufgaben.AufgabenGruppeList;
|
import java.util.Base64;
|
||||||
import de.oaa.xxx.aufgaben.AufgabenGruppePage;
|
import java.util.UUID;
|
||||||
import de.oaa.xxx.aufgaben.entity.AufgabeEntity;
|
|
||||||
import de.oaa.xxx.aufgaben.entity.AufgabenGruppeEntity;
|
|
||||||
import de.oaa.xxx.aufgaben.entity.FinisherEntity;
|
|
||||||
import de.oaa.xxx.aufgaben.entity.SperreEntity;
|
|
||||||
import de.oaa.xxx.aufgaben.entity.StrafeEntity;
|
|
||||||
import de.oaa.xxx.aufgaben.entity.ToyEntity;
|
|
||||||
import de.oaa.xxx.aufgaben.repository.AufgabeRepository;
|
|
||||||
import de.oaa.xxx.aufgaben.repository.AufgabenGruppeRepository;
|
|
||||||
import de.oaa.xxx.aufgaben.repository.FinisherRepository;
|
|
||||||
import de.oaa.xxx.aufgaben.repository.GruppenAboRepository;
|
|
||||||
import de.oaa.xxx.aufgaben.repository.SperreRepository;
|
|
||||||
import de.oaa.xxx.aufgaben.repository.StrafeRepository;
|
|
||||||
import de.oaa.xxx.aufgaben.repository.ToyRepository;
|
|
||||||
import de.oaa.xxx.user.UserEntity;
|
|
||||||
import de.oaa.xxx.user.UserRepository;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
@@ -37,15 +23,19 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||||
|
|
||||||
import java.security.Principal;
|
import de.oaa.xxx.aufgaben.AufgabenGruppe;
|
||||||
import java.util.ArrayList;
|
import de.oaa.xxx.aufgaben.AufgabenGruppeList;
|
||||||
import java.util.Base64;
|
import de.oaa.xxx.aufgaben.AufgabenGruppePage;
|
||||||
import java.util.HashMap;
|
import de.oaa.xxx.aufgaben.AufgabenGruppeService;
|
||||||
import java.util.HashSet;
|
import de.oaa.xxx.aufgaben.entity.AufgabenGruppeEntity;
|
||||||
import java.util.List;
|
import de.oaa.xxx.aufgaben.repository.AufgabeRepository;
|
||||||
import java.util.Map;
|
import de.oaa.xxx.aufgaben.repository.AufgabenGruppeRepository;
|
||||||
import java.util.Set;
|
import de.oaa.xxx.aufgaben.repository.FinisherRepository;
|
||||||
import java.util.UUID;
|
import de.oaa.xxx.aufgaben.repository.GruppenAboRepository;
|
||||||
|
import de.oaa.xxx.aufgaben.repository.SperreRepository;
|
||||||
|
import de.oaa.xxx.aufgaben.repository.StrafeRepository;
|
||||||
|
import de.oaa.xxx.user.UserEntity;
|
||||||
|
import de.oaa.xxx.user.UserRepository;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/gruppe")
|
@RequestMapping("/gruppe")
|
||||||
@@ -62,7 +52,7 @@ public class AufgabenGruppeController {
|
|||||||
private final FinisherRepository finisherRepository;
|
private final FinisherRepository finisherRepository;
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final GruppenAboRepository aboRepository;
|
private final GruppenAboRepository aboRepository;
|
||||||
private final ToyRepository toyRepository;
|
private final AufgabenGruppeService aufgabenGruppeService;
|
||||||
|
|
||||||
public AufgabenGruppeController(AufgabenGruppeRepository gruppeRepository,
|
public AufgabenGruppeController(AufgabenGruppeRepository gruppeRepository,
|
||||||
AufgabeRepository aufgabeRepository,
|
AufgabeRepository aufgabeRepository,
|
||||||
@@ -71,7 +61,7 @@ public class AufgabenGruppeController {
|
|||||||
FinisherRepository finisherRepository,
|
FinisherRepository finisherRepository,
|
||||||
UserRepository userRepository,
|
UserRepository userRepository,
|
||||||
GruppenAboRepository aboRepository,
|
GruppenAboRepository aboRepository,
|
||||||
ToyRepository toyRepository) {
|
AufgabenGruppeService aufgabenGruppeService) {
|
||||||
this.gruppeRepository = gruppeRepository;
|
this.gruppeRepository = gruppeRepository;
|
||||||
this.aufgabeRepository = aufgabeRepository;
|
this.aufgabeRepository = aufgabeRepository;
|
||||||
this.strafeRepository = strafeRepository;
|
this.strafeRepository = strafeRepository;
|
||||||
@@ -79,7 +69,7 @@ public class AufgabenGruppeController {
|
|||||||
this.finisherRepository = finisherRepository;
|
this.finisherRepository = finisherRepository;
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.aboRepository = aboRepository;
|
this.aboRepository = aboRepository;
|
||||||
this.toyRepository = toyRepository;
|
this.aufgabenGruppeService = aufgabenGruppeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Paginierte Listen ──
|
// ── Paginierte Listen ──
|
||||||
@@ -190,118 +180,16 @@ public class AufgabenGruppeController {
|
|||||||
public ResponseEntity<Void> copy(@PathVariable UUID gruppeId, Principal principal) {
|
public ResponseEntity<Void> copy(@PathVariable UUID gruppeId, Principal principal) {
|
||||||
UserEntity user = resolveUser(principal);
|
UserEntity user = resolveUser(principal);
|
||||||
if (user == null) return ResponseEntity.status(401).build();
|
if (user == null) return ResponseEntity.status(401).build();
|
||||||
|
try {
|
||||||
if (gruppeRepository.countByUserId(user.getUserId()) >= 10) {
|
aufgabenGruppeService.copyGruppe(gruppeId, user.getUserId());
|
||||||
|
return ResponseEntity.status(201).build();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
return ResponseEntity.status(409).build();
|
return ResponseEntity.status(409).build();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
String msg = e.getMessage();
|
||||||
|
if (msg != null && msg.contains("nicht gefunden")) return ResponseEntity.notFound().build();
|
||||||
|
return ResponseEntity.status(403).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
AufgabenGruppeEntity source = gruppeRepository.findById(gruppeId).orElse(null);
|
|
||||||
if (source == null) return ResponseEntity.notFound().build();
|
|
||||||
if (source.isPrivateGruppe()) return ResponseEntity.status(403).build();
|
|
||||||
if (user.getUserId().equals(source.getUserId())) return ResponseEntity.status(403).build();
|
|
||||||
|
|
||||||
// Build toy mapping: source toyId → toy entity the copy will reference
|
|
||||||
Set<ToyEntity> allSourceToys = new HashSet<>();
|
|
||||||
source.getAufgaben().forEach(a -> { if (a.getBenoetigteToys() != null) allSourceToys.addAll(a.getBenoetigteToys()); });
|
|
||||||
source.getStrafen().forEach(s -> { if (s.getBenoetigteToys() != null) allSourceToys.addAll(s.getBenoetigteToys()); });
|
|
||||||
source.getSperren().forEach(sp -> { if (sp.getBenoetigteToys() != null) allSourceToys.addAll(sp.getBenoetigteToys()); });
|
|
||||||
source.getFinisher().forEach(f -> { if (f.getBenoetigteToys() != null) allSourceToys.addAll(f.getBenoetigteToys()); });
|
|
||||||
|
|
||||||
Map<UUID, ToyEntity> toyMapping = new HashMap<>();
|
|
||||||
for (ToyEntity sourceToy : allSourceToys) {
|
|
||||||
if (sourceToy.getUserId() == null) {
|
|
||||||
// System toy – reference directly
|
|
||||||
toyMapping.put(sourceToy.getToyId(), sourceToy);
|
|
||||||
} else {
|
|
||||||
// User toy – find existing toy with same name in user's collection, or create a copy
|
|
||||||
ToyEntity mapped = toyRepository.findByNameIgnoreCaseAndUserId(sourceToy.getName(), user.getUserId())
|
|
||||||
.orElseGet(() -> {
|
|
||||||
ToyEntity tc = new ToyEntity();
|
|
||||||
tc.setToyId(UUID.randomUUID());
|
|
||||||
tc.setName(sourceToy.getName());
|
|
||||||
tc.setBeschreibung(sourceToy.getBeschreibung());
|
|
||||||
tc.setBild(sourceToy.getBild());
|
|
||||||
tc.setUserId(user.getUserId());
|
|
||||||
return toyRepository.save(tc);
|
|
||||||
});
|
|
||||||
toyMapping.put(sourceToy.getToyId(), mapped);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AufgabenGruppeEntity copy = new AufgabenGruppeEntity();
|
|
||||||
copy.setGruppenId(UUID.randomUUID());
|
|
||||||
copy.setName(source.getName());
|
|
||||||
copy.setBeschreibung(source.getBeschreibung());
|
|
||||||
copy.setVon(source.getVon());
|
|
||||||
copy.setBild(source.getBild());
|
|
||||||
copy.setUserId(user.getUserId());
|
|
||||||
copy.setPrivateGruppe(true);
|
|
||||||
gruppeRepository.save(copy);
|
|
||||||
|
|
||||||
for (AufgabeEntity a : source.getAufgaben()) {
|
|
||||||
AufgabeEntity ac = new AufgabeEntity();
|
|
||||||
ac.setAufgabeId(UUID.randomUUID());
|
|
||||||
ac.setAufgabenGruppe(copy);
|
|
||||||
ac.setKurzText(a.getKurzText());
|
|
||||||
ac.setText(a.getText());
|
|
||||||
ac.setLevel(a.getLevel());
|
|
||||||
ac.setSekundenVon(a.getSekundenVon());
|
|
||||||
ac.setSekundenBis(a.getSekundenBis());
|
|
||||||
ac.setBenoetigtAktiv(a.getBenoetigtAktiv() != null ? new ArrayList<>(a.getBenoetigtAktiv()) : null);
|
|
||||||
ac.setBenoetigtPassiv(a.getBenoetigtPassiv() != null ? new ArrayList<>(a.getBenoetigtPassiv()) : null);
|
|
||||||
ac.setBenoetigteToys(mapToys(a.getBenoetigteToys(), toyMapping));
|
|
||||||
aufgabeRepository.save(ac);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (StrafeEntity s : source.getStrafen()) {
|
|
||||||
StrafeEntity sc = new StrafeEntity();
|
|
||||||
sc.setStrafeId(UUID.randomUUID());
|
|
||||||
sc.setAufgabenGruppe(copy);
|
|
||||||
sc.setKurzText(s.getKurzText());
|
|
||||||
sc.setText(s.getText());
|
|
||||||
sc.setLevel(s.getLevel());
|
|
||||||
sc.setSekundenVon(s.getSekundenVon());
|
|
||||||
sc.setSekundenBis(s.getSekundenBis());
|
|
||||||
sc.setBenoetigtAktiv(s.getBenoetigtAktiv() != null ? new ArrayList<>(s.getBenoetigtAktiv()) : null);
|
|
||||||
sc.setBenoetigtPassiv(s.getBenoetigtPassiv() != null ? new ArrayList<>(s.getBenoetigtPassiv()) : null);
|
|
||||||
sc.setBenoetigteToys(mapToys(s.getBenoetigteToys(), toyMapping));
|
|
||||||
strafeRepository.save(sc);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (SperreEntity sp : source.getSperren()) {
|
|
||||||
SperreEntity spc = new SperreEntity();
|
|
||||||
spc.setSperreId(UUID.randomUUID());
|
|
||||||
spc.setAufgabenGruppe(copy);
|
|
||||||
spc.setKurzText(sp.getKurzText());
|
|
||||||
spc.setText(sp.getText());
|
|
||||||
spc.setReleaseText(sp.getReleaseText());
|
|
||||||
spc.setMinutenVon(sp.getMinutenVon());
|
|
||||||
spc.setMinutenBis(sp.getMinutenBis());
|
|
||||||
spc.setSperreFuer(sp.getSperreFuer() != null ? new ArrayList<>(sp.getSperreFuer()) : null);
|
|
||||||
spc.setBenoetigteToys(mapToys(sp.getBenoetigteToys(), toyMapping));
|
|
||||||
sperreRepository.save(spc);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (FinisherEntity f : source.getFinisher()) {
|
|
||||||
FinisherEntity fc = new FinisherEntity();
|
|
||||||
fc.setFinisherId(UUID.randomUUID());
|
|
||||||
fc.setAufgabenGruppe(copy);
|
|
||||||
fc.setKurzText(f.getKurzText());
|
|
||||||
fc.setText(f.getText());
|
|
||||||
fc.setGeschlecht(f.getGeschlecht());
|
|
||||||
fc.setBenoetigtAktiv(f.getBenoetigtAktiv() != null ? new ArrayList<>(f.getBenoetigtAktiv()) : null);
|
|
||||||
fc.setBenoetigtPassiv(f.getBenoetigtPassiv() != null ? new ArrayList<>(f.getBenoetigtPassiv()) : null);
|
|
||||||
fc.setBenoetigteToys(mapToys(f.getBenoetigteToys(), toyMapping));
|
|
||||||
finisherRepository.save(fc);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.info("User {} hat AufgabenGruppe {} kopiert (Quelle: {})", user.getUserId(), copy.getGruppenId(), gruppeId);
|
|
||||||
return ResponseEntity.status(201).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ToyEntity> mapToys(List<ToyEntity> source, Map<UUID, ToyEntity> mapping) {
|
|
||||||
if (source == null || source.isEmpty()) return new ArrayList<>();
|
|
||||||
return source.stream().map(t -> mapping.getOrDefault(t.getToyId(), t)).toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Löschen ──
|
// ── Löschen ──
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
@@ -49,6 +51,9 @@ public class SecurityConfig {
|
|||||||
.requestMatchers("/sessionbdsmtasks.html").authenticated()
|
.requestMatchers("/sessionbdsmtasks.html").authenticated()
|
||||||
.requestMatchers("/sessionbdsmtoys.html").authenticated()
|
.requestMatchers("/sessionbdsmtoys.html").authenticated()
|
||||||
.requestMatchers("/sessionbdsmingame.html").authenticated()
|
.requestMatchers("/sessionbdsmingame.html").authenticated()
|
||||||
|
.requestMatchers("/neubdsm.html").authenticated()
|
||||||
|
.requestMatchers("/bdsmingame.html").authenticated()
|
||||||
|
.requestMatchers("/bdsmwarten.html").authenticated()
|
||||||
.requestMatchers("/personen-suchen.html").authenticated()
|
.requestMatchers("/personen-suchen.html").authenticated()
|
||||||
.requestMatchers("/freunde.html").authenticated()
|
.requestMatchers("/freunde.html").authenticated()
|
||||||
.requestMatchers("/nachrichten.html").authenticated()
|
.requestMatchers("/nachrichten.html").authenticated()
|
||||||
@@ -79,6 +84,7 @@ public class SecurityConfig {
|
|||||||
.requestMatchers("/*.svg").permitAll()
|
.requestMatchers("/*.svg").permitAll()
|
||||||
.requestMatchers("/*.webp").permitAll()
|
.requestMatchers("/*.webp").permitAll()
|
||||||
.requestMatchers(HttpMethod.GET, "/login").permitAll()
|
.requestMatchers(HttpMethod.GET, "/login").permitAll()
|
||||||
|
.requestMatchers(HttpMethod.POST, "/login").permitAll()
|
||||||
.requestMatchers(HttpMethod.GET, "/login/publickey").permitAll()
|
.requestMatchers(HttpMethod.GET, "/login/publickey").permitAll()
|
||||||
.requestMatchers(HttpMethod.GET, "/login/logout").permitAll()
|
.requestMatchers(HttpMethod.GET, "/login/logout").permitAll()
|
||||||
.requestMatchers(HttpMethod.POST, "/user").permitAll()
|
.requestMatchers(HttpMethod.POST, "/user").permitAll()
|
||||||
@@ -96,4 +102,9 @@ public class SecurityConfig {
|
|||||||
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
|
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,208 @@
|
|||||||
|
package de.oaa.xxx.games.bdsm;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import de.oaa.xxx.games.bdsm.entity.BdsmGameEntity;
|
||||||
|
import de.oaa.xxx.games.bdsm.repository.AktiveSperreRepository;
|
||||||
|
import de.oaa.xxx.games.bdsm.repository.BdsmGameRepository;
|
||||||
|
import de.oaa.xxx.games.bdsm.repository.MitspielerRepository;
|
||||||
|
import de.oaa.xxx.games.chastity.cardlock.CardLockEntity;
|
||||||
|
import de.oaa.xxx.games.chastity.cardlock.CardlockRepository;
|
||||||
|
import de.oaa.xxx.games.history.GameHistoryEntity;
|
||||||
|
import de.oaa.xxx.games.history.GameHistoryRepository;
|
||||||
|
import de.oaa.xxx.games.history.GameRole;
|
||||||
|
import de.oaa.xxx.games.history.GameType;
|
||||||
|
import de.oaa.xxx.social.SystemMessageService;
|
||||||
|
import de.oaa.xxx.social.entity.MessageCause;
|
||||||
|
import de.oaa.xxx.user.UserRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service für komplexe BDSM-Game-Operationen.
|
||||||
|
* Kapselt Spielabschluss-Logik (XP-Vergabe, History) und den BDSM→Chastity-Übergang.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class BdsmGameService {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(BdsmGameService.class);
|
||||||
|
|
||||||
|
private final BdsmGameRepository sessionRepository;
|
||||||
|
private final MitspielerRepository mitspielerRepository;
|
||||||
|
private final AktiveSperreRepository aktiveSperreRepository;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final GameHistoryRepository gameHistoryRepository;
|
||||||
|
private final CardlockRepository cardlockRepository;
|
||||||
|
private final SystemMessageService systemMessageService;
|
||||||
|
|
||||||
|
public BdsmGameService(BdsmGameRepository sessionRepository,
|
||||||
|
MitspielerRepository mitspielerRepository,
|
||||||
|
AktiveSperreRepository aktiveSperreRepository,
|
||||||
|
UserRepository userRepository,
|
||||||
|
GameHistoryRepository gameHistoryRepository,
|
||||||
|
CardlockRepository cardlockRepository,
|
||||||
|
SystemMessageService systemMessageService) {
|
||||||
|
this.sessionRepository = sessionRepository;
|
||||||
|
this.mitspielerRepository = mitspielerRepository;
|
||||||
|
this.aktiveSperreRepository = aktiveSperreRepository;
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.gameHistoryRepository = gameHistoryRepository;
|
||||||
|
this.cardlockRepository = cardlockRepository;
|
||||||
|
this.systemMessageService = systemMessageService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Beendet eine BDSM-Session ordentlich: History speichern, XP vergeben,
|
||||||
|
* Gäste auf eigenem Gerät benachrichtigen, Daten aufräumen.
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public void spielAbschliessen(BdsmGameEntity entity) {
|
||||||
|
LocalDateTime endTime = LocalDateTime.now();
|
||||||
|
long durationMinutes = Duration.between(entity.getStartZeit(), endTime).toMinutes();
|
||||||
|
|
||||||
|
GameHistoryEntity entry = new GameHistoryEntity();
|
||||||
|
entry.setGameName("BDSM Game");
|
||||||
|
entry.setGameType(GameType.BDSM);
|
||||||
|
entry.setStartTime(entity.getStartZeit());
|
||||||
|
entry.setEndTime(endTime);
|
||||||
|
entry.setDurationMinutes(durationMinutes);
|
||||||
|
entry.addParticipant(entity.getUserId(), GameRole.PLAYER);
|
||||||
|
entity.getMitspieler().stream()
|
||||||
|
.filter(m -> m.getUserId() != null)
|
||||||
|
.forEach(m -> entry.addParticipant(m.getUserId(), GameRole.PLAYER));
|
||||||
|
gameHistoryRepository.save(entry);
|
||||||
|
|
||||||
|
int xp = (int) durationMinutes;
|
||||||
|
userRepository.findById(entity.getUserId()).ifPresent(u -> {
|
||||||
|
u.setBdsmXp(u.getBdsmXp() + xp);
|
||||||
|
userRepository.save(u);
|
||||||
|
});
|
||||||
|
entity.getMitspieler().stream()
|
||||||
|
.filter(m -> m.getUserId() != null)
|
||||||
|
.forEach(m -> userRepository.findById(m.getUserId()).ifPresent(u -> {
|
||||||
|
u.setBdsmXp(u.getBdsmXp() + xp);
|
||||||
|
userRepository.save(u);
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Gäste auf eigenem Gerät benachrichtigen
|
||||||
|
String endNachricht = "Das BDSM-Spiel wurde erfolgreich beendet. Danke fürs Mitspielen! 🎉";
|
||||||
|
entity.getMitspieler().stream()
|
||||||
|
.filter(m -> m.isEigenesGeraet() && m.getUserId() != null)
|
||||||
|
.forEach(m -> systemMessageService.send(entity.getUserId(), m.getUserId(),
|
||||||
|
endNachricht, "/userhome.html", MessageCause.GAME_STATE));
|
||||||
|
|
||||||
|
bereinige(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Überführt eine BDSM-Session in ein neues Chastity-Lock (BDSM→Chastity-Transition).
|
||||||
|
* History + XP werden wie beim normalen Spielabschluss vergeben.
|
||||||
|
*
|
||||||
|
* @return Das neu angelegte CardLockEntity
|
||||||
|
* @throws IllegalArgumentException wenn Session oder Template nicht gefunden
|
||||||
|
* @throws IllegalStateException wenn Lockee bereits ein aktives Lock hat
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public CardLockEntity zuChastity(UUID sessionId, UUID templateLockId, UUID lockeeUserId, UUID keyholderUserId) {
|
||||||
|
BdsmGameEntity entity = sessionRepository.findById(sessionId)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("Session nicht gefunden: " + sessionId));
|
||||||
|
|
||||||
|
CardLockEntity template = cardlockRepository.findById(templateLockId)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("Template-Lock nicht gefunden: " + templateLockId));
|
||||||
|
|
||||||
|
if (lockeeUserId != null
|
||||||
|
&& cardlockRepository.existsByLockeeAndStartTimeIsNotNullAndUnlockTimeIsNull(lockeeUserId)) {
|
||||||
|
throw new IllegalStateException("Lockee hat bereits ein aktives Chastity-Lock");
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
CardLockEntity newLock = new CardLockEntity();
|
||||||
|
newLock.setName(template.getName());
|
||||||
|
newLock.setLockee(lockeeUserId);
|
||||||
|
newLock.setKeyholder(keyholderUserId);
|
||||||
|
newLock.setInitialCards(template.getInitialCards());
|
||||||
|
newLock.setPickEveryMinute(template.getPickEveryMinute());
|
||||||
|
newLock.setAccumulatePicks(template.isAccumulatePicks());
|
||||||
|
newLock.setShowRemainingCards(template.isShowRemainingCards());
|
||||||
|
newLock.setLatestOpeningtime(template.getLatestOpeningtime());
|
||||||
|
newLock.setHygineOpeningDurationMinutes(template.getHygineOpeningDurationMinutes());
|
||||||
|
newLock.setHygineOpeningEveryMinites(template.getHygineOpeningEveryMinites());
|
||||||
|
newLock.setTasks(template.getTasks());
|
||||||
|
newLock.setRequiresVerification(template.isRequiresVerification());
|
||||||
|
newLock.setTestLock(false);
|
||||||
|
newLock.setTaskCardMode(template.getTaskCardMode());
|
||||||
|
|
||||||
|
int codeLines = template.getUnlockCodeLines() != null ? template.getUnlockCodeLines() : 5;
|
||||||
|
newLock.setUnlockCodeLines(codeLines);
|
||||||
|
StringBuilder codeBuilder = new StringBuilder();
|
||||||
|
java.util.Random rng = new java.util.Random();
|
||||||
|
for (int i = 0; i < codeLines; i++) codeBuilder.append(rng.nextInt(10));
|
||||||
|
newLock.setUnlockCode(codeBuilder.toString());
|
||||||
|
|
||||||
|
newLock.setStartTime(now);
|
||||||
|
newLock.setAvailableCards(template.getInitialCards() != null
|
||||||
|
? new ArrayList<>(template.getInitialCards()) : new ArrayList<>());
|
||||||
|
newLock.setOpenPicks(0);
|
||||||
|
if (template.getPickEveryMinute() != null) {
|
||||||
|
newLock.setNextCardIn(now.plusMinutes(template.getPickEveryMinute()));
|
||||||
|
}
|
||||||
|
if (template.getHygineOpeningEveryMinites() != null) {
|
||||||
|
newLock.setLastHygineOpening(now);
|
||||||
|
}
|
||||||
|
cardlockRepository.save(newLock);
|
||||||
|
|
||||||
|
// Lockee benachrichtigen
|
||||||
|
if (lockeeUserId != null) {
|
||||||
|
userRepository.findById(keyholderUserId).ifPresent(keyholder ->
|
||||||
|
systemMessageService.send(keyholderUserId, lockeeUserId,
|
||||||
|
keyholder.getName() + " hat nach dem BDSM Game ein Chastity Lock auf dich gesetzt.",
|
||||||
|
"/activelock.html", MessageCause.GAME_STATE));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spielabschluss-Logik (History + XP + Cleanup)
|
||||||
|
LocalDateTime endTime = LocalDateTime.now();
|
||||||
|
long durationMinutes = Duration.between(entity.getStartZeit(), endTime).toMinutes();
|
||||||
|
GameHistoryEntity entry = new GameHistoryEntity();
|
||||||
|
entry.setGameName("BDSM Game");
|
||||||
|
entry.setGameType(GameType.BDSM);
|
||||||
|
entry.setStartTime(entity.getStartZeit());
|
||||||
|
entry.setEndTime(endTime);
|
||||||
|
entry.setDurationMinutes(durationMinutes);
|
||||||
|
entry.addParticipant(entity.getUserId(), GameRole.PLAYER);
|
||||||
|
entity.getMitspieler().stream()
|
||||||
|
.filter(m -> m.getUserId() != null)
|
||||||
|
.forEach(m -> entry.addParticipant(m.getUserId(), GameRole.PLAYER));
|
||||||
|
gameHistoryRepository.save(entry);
|
||||||
|
|
||||||
|
int xp = (int) durationMinutes;
|
||||||
|
userRepository.findById(entity.getUserId()).ifPresent(u -> {
|
||||||
|
u.setBdsmXp(u.getBdsmXp() + xp);
|
||||||
|
userRepository.save(u);
|
||||||
|
});
|
||||||
|
entity.getMitspieler().stream()
|
||||||
|
.filter(m -> m.getUserId() != null)
|
||||||
|
.forEach(m -> userRepository.findById(m.getUserId()).ifPresent(u -> {
|
||||||
|
u.setBdsmXp(u.getBdsmXp() + xp);
|
||||||
|
userRepository.save(u);
|
||||||
|
}));
|
||||||
|
|
||||||
|
bereinige(entity);
|
||||||
|
|
||||||
|
LOGGER.info("BDSM-Session {} in Chastity-Lock {} überführt (Lockee: {}, Keyholder: {})",
|
||||||
|
sessionId, newLock.getLockId(), lockeeUserId, keyholderUserId);
|
||||||
|
return newLock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Löscht alle Session-Daten (Sperren, Mitspieler, Session selbst). */
|
||||||
|
private void bereinige(BdsmGameEntity entity) {
|
||||||
|
aktiveSperreRepository.deleteAll(entity.getAktiveSperren());
|
||||||
|
mitspielerRepository.deleteAll(entity.getMitspieler());
|
||||||
|
sessionRepository.delete(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -67,6 +67,8 @@ public class BdsmEinladungController {
|
|||||||
return ResponseEntity.status(403).build();
|
return ResponseEntity.status(403).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.setupId() == null) return ResponseEntity.badRequest().build();
|
||||||
|
|
||||||
// Prüfen ob Person bereits aktiv eingeladen oder Teil des Spiels
|
// Prüfen ob Person bereits aktiv eingeladen oder Teil des Spiels
|
||||||
boolean alreadyInvited = einladungRepository.findBySetupId(req.setupId()).stream()
|
boolean alreadyInvited = einladungRepository.findBySetupId(req.setupId()).stream()
|
||||||
.anyMatch(e -> req.inviteeId().equals(e.getInviteeId())
|
.anyMatch(e -> req.inviteeId().equals(e.getInviteeId())
|
||||||
@@ -113,6 +115,10 @@ public class BdsmEinladungController {
|
|||||||
if (e == null) return ResponseEntity.notFound().build();
|
if (e == null) return ResponseEntity.notFound().build();
|
||||||
if (!e.getInviterId().equals(userId)) return ResponseEntity.status(403).build();
|
if (!e.getInviterId().equals(userId)) return ResponseEntity.status(403).build();
|
||||||
e.setStatus(Status.CANCELLED);
|
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.",
|
||||||
|
"/einladungen.html", MessageCause.INVITATION);
|
||||||
return ResponseEntity.accepted().build();
|
return ResponseEntity.accepted().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +174,7 @@ public class BdsmEinladungController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
public ResponseEntity<Map<String, Object>> getById(@PathVariable UUID id, Principal principal) {
|
public ResponseEntity<Map<String, Object>> getById(@PathVariable("id") UUID id, Principal principal) {
|
||||||
UUID userId = currentUserId(principal);
|
UUID userId = currentUserId(principal);
|
||||||
if (userId == null) return ResponseEntity.status(401).build();
|
if (userId == null) return ResponseEntity.status(401).build();
|
||||||
BdsmEinladungEntity e = einladungRepository.findById(id).orElse(null);
|
BdsmEinladungEntity e = einladungRepository.findById(id).orElse(null);
|
||||||
|
|||||||
@@ -1,34 +1,17 @@
|
|||||||
package de.oaa.xxx.games.bdsm.controller;
|
package de.oaa.xxx.games.bdsm.controller;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import java.security.Principal;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import java.time.Duration;
|
||||||
import de.oaa.xxx.games.bdsm.AufgabeAnzeige;
|
import java.time.LocalDateTime;
|
||||||
import de.oaa.xxx.games.bdsm.Mitspieler;
|
import java.util.ArrayList;
|
||||||
import de.oaa.xxx.games.bdsm.GeschlechtEnum;
|
import java.util.Collections;
|
||||||
import de.oaa.xxx.games.bdsm.Werkzeug;
|
import java.util.HashSet;
|
||||||
import de.oaa.xxx.games.bdsm.BdsmGame;
|
import java.util.LinkedHashMap;
|
||||||
import de.oaa.xxx.games.bdsm.BdsmGameDurchfuehren;
|
import java.util.List;
|
||||||
import de.oaa.xxx.games.bdsm.aufgaben.AufgabenList;
|
import java.util.Map;
|
||||||
import de.oaa.xxx.games.bdsm.sperre.SperreCallback;
|
import java.util.Set;
|
||||||
import de.oaa.xxx.games.bdsm.sperre.SperrenVerlaengernCallback;
|
import java.util.UUID;
|
||||||
import de.oaa.xxx.games.bdsm.sperre.SperreVerarbeiten;
|
|
||||||
import de.oaa.xxx.games.bdsm.entity.MitspielerEntity;
|
|
||||||
import de.oaa.xxx.games.bdsm.entity.BdsmGameEntity;
|
|
||||||
import de.oaa.xxx.games.bdsm.entity.AktiveSperreEntity;
|
|
||||||
import de.oaa.xxx.games.bdsm.entity.BdsmEinladungEntity;
|
|
||||||
import de.oaa.xxx.games.bdsm.repository.AktiveSperreRepository;
|
|
||||||
import de.oaa.xxx.games.bdsm.repository.BdsmEinladungRepository;
|
|
||||||
import de.oaa.xxx.games.bdsm.repository.MitspielerRepository;
|
|
||||||
import de.oaa.xxx.games.bdsm.repository.BdsmGameRepository;
|
|
||||||
import de.oaa.xxx.games.chastity.cardlock.CardLockEntity;
|
|
||||||
import de.oaa.xxx.games.chastity.cardlock.CardlockRepository;
|
|
||||||
import de.oaa.xxx.games.history.GameHistoryEntity;
|
|
||||||
import de.oaa.xxx.games.history.GameHistoryRepository;
|
|
||||||
import de.oaa.xxx.games.history.GameRole;
|
|
||||||
import de.oaa.xxx.games.history.GameType;
|
|
||||||
import de.oaa.xxx.social.SystemMessageService;
|
|
||||||
import de.oaa.xxx.social.entity.MessageCause;
|
|
||||||
import de.oaa.xxx.user.UserRepository;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -45,52 +28,71 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||||
|
|
||||||
import java.security.Principal;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import java.time.Duration;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.ArrayList;
|
import de.oaa.xxx.games.bdsm.AufgabeAnzeige;
|
||||||
import java.util.Collections;
|
import de.oaa.xxx.games.bdsm.BdsmGame;
|
||||||
import java.util.HashSet;
|
import de.oaa.xxx.games.bdsm.BdsmGameDurchfuehren;
|
||||||
import java.util.LinkedHashMap;
|
import de.oaa.xxx.games.bdsm.BdsmGameService;
|
||||||
import java.util.List;
|
import de.oaa.xxx.games.bdsm.GeschlechtEnum;
|
||||||
import java.util.Map;
|
import de.oaa.xxx.games.bdsm.Mitspieler;
|
||||||
import java.util.Set;
|
import de.oaa.xxx.games.bdsm.Werkzeug;
|
||||||
import java.util.UUID;
|
import de.oaa.xxx.games.bdsm.aufgaben.AufgabenList;
|
||||||
|
import de.oaa.xxx.games.bdsm.entity.AktiveSperreEntity;
|
||||||
|
import de.oaa.xxx.games.bdsm.entity.BdsmEinladungEntity;
|
||||||
|
import de.oaa.xxx.games.bdsm.entity.BdsmGameEntity;
|
||||||
|
import de.oaa.xxx.games.bdsm.entity.MitspielerEntity;
|
||||||
|
import de.oaa.xxx.games.bdsm.repository.AktiveSperreRepository;
|
||||||
|
import de.oaa.xxx.games.bdsm.repository.BdsmEinladungRepository;
|
||||||
|
import de.oaa.xxx.games.bdsm.repository.BdsmGameRepository;
|
||||||
|
import de.oaa.xxx.games.bdsm.repository.MitspielerRepository;
|
||||||
|
import de.oaa.xxx.games.bdsm.sperre.SperreCallback;
|
||||||
|
import de.oaa.xxx.games.bdsm.sperre.SperreVerarbeiten;
|
||||||
|
import de.oaa.xxx.games.bdsm.sperre.SperrenVerlaengernCallback;
|
||||||
|
import de.oaa.xxx.games.chastity.cardlock.CardLockEntity;
|
||||||
|
import de.oaa.xxx.games.chastity.cardlock.CardlockRepository;
|
||||||
|
import de.oaa.xxx.social.SystemMessageService;
|
||||||
|
import de.oaa.xxx.social.entity.MessageCause;
|
||||||
|
import de.oaa.xxx.user.UserRepository;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/bdsm")
|
@RequestMapping("/bdsm")
|
||||||
@Transactional
|
@Transactional
|
||||||
public class BdsmGameController {
|
public class BdsmGameController {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(BdsmGameController.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(BdsmGameController.class);
|
||||||
/** Kurzlebiger In-Memory-Marker: Sessions die ordentlich über spielAbgeschlossen beendet wurden. */
|
/**
|
||||||
private static final Set<UUID> ORDENTLICH_BEENDET = Collections.synchronizedSet(new HashSet<>());
|
* Kurzlebiger In-Memory-Marker: Sessions die ordentlich über spielAbgeschlossen
|
||||||
|
* beendet wurden.
|
||||||
|
*/
|
||||||
|
private static final Set<UUID> ORDENTLICH_BEENDET = Collections.synchronizedSet(new HashSet<>());
|
||||||
|
|
||||||
private final BdsmGameRepository sessionRepository;
|
private final BdsmGameRepository sessionRepository;
|
||||||
private final MitspielerRepository mitspielerRepository;
|
private final MitspielerRepository mitspielerRepository;
|
||||||
private final AktiveSperreRepository aktiveSperreRepository;
|
private final AktiveSperreRepository aktiveSperreRepository;
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final GameHistoryRepository gameHistoryRepository;
|
private final BdsmEinladungRepository einladungRepository;
|
||||||
private final BdsmEinladungRepository einladungRepository;
|
private final ObjectMapper objectMapper;
|
||||||
private final ObjectMapper objectMapper;
|
private final SystemMessageService systemMessageService;
|
||||||
private final SystemMessageService systemMessageService;
|
private final CardlockRepository cardlockRepository;
|
||||||
private final CardlockRepository cardlockRepository;
|
private final BdsmGameService bdsmGameService;
|
||||||
|
|
||||||
public BdsmGameController(BdsmGameRepository sessionRepository, MitspielerRepository mitspielerRepository,
|
public BdsmGameController(BdsmGameRepository sessionRepository, MitspielerRepository mitspielerRepository,
|
||||||
AktiveSperreRepository aktiveSperreRepository, UserRepository userRepository,
|
AktiveSperreRepository aktiveSperreRepository, UserRepository userRepository,
|
||||||
GameHistoryRepository gameHistoryRepository, BdsmEinladungRepository einladungRepository,
|
BdsmEinladungRepository einladungRepository, ObjectMapper objectMapper,
|
||||||
ObjectMapper objectMapper, SystemMessageService systemMessageService,
|
SystemMessageService systemMessageService, CardlockRepository cardlockRepository,
|
||||||
CardlockRepository cardlockRepository) {
|
BdsmGameService bdsmGameService) {
|
||||||
this.sessionRepository = sessionRepository;
|
this.sessionRepository = sessionRepository;
|
||||||
this.mitspielerRepository = mitspielerRepository;
|
this.mitspielerRepository = mitspielerRepository;
|
||||||
this.aktiveSperreRepository = aktiveSperreRepository;
|
this.aktiveSperreRepository = aktiveSperreRepository;
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.gameHistoryRepository = gameHistoryRepository;
|
this.einladungRepository = einladungRepository;
|
||||||
this.einladungRepository = einladungRepository;
|
this.objectMapper = objectMapper;
|
||||||
this.objectMapper = objectMapper;
|
this.systemMessageService = systemMessageService;
|
||||||
this.systemMessageService = systemMessageService;
|
this.cardlockRepository = cardlockRepository;
|
||||||
this.cardlockRepository = cardlockRepository;
|
this.bdsmGameService = bdsmGameService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{sessionId}")
|
@GetMapping("/{sessionId}")
|
||||||
public ResponseEntity<BdsmGame> getBySessionId(@PathVariable UUID sessionId) {
|
public ResponseEntity<BdsmGame> getBySessionId(@PathVariable UUID sessionId) {
|
||||||
@@ -159,46 +161,8 @@ public class BdsmGameController {
|
|||||||
public ResponseEntity<Void> spielAbgeschlossen(@PathVariable UUID sessionId) {
|
public ResponseEntity<Void> spielAbgeschlossen(@PathVariable UUID sessionId) {
|
||||||
BdsmGameEntity entity = sessionRepository.findById(sessionId).orElse(null);
|
BdsmGameEntity entity = sessionRepository.findById(sessionId).orElse(null);
|
||||||
if (entity == null) return ResponseEntity.notFound().build();
|
if (entity == null) return ResponseEntity.notFound().build();
|
||||||
|
|
||||||
LocalDateTime endTime = LocalDateTime.now();
|
|
||||||
long durationMinutes = Duration.between(entity.getStartZeit(), endTime).toMinutes();
|
|
||||||
|
|
||||||
GameHistoryEntity entry = new GameHistoryEntity();
|
|
||||||
entry.setGameName("BDSM Game");
|
|
||||||
entry.setGameType(GameType.BDSM);
|
|
||||||
entry.setStartTime(entity.getStartZeit());
|
|
||||||
entry.setEndTime(endTime);
|
|
||||||
entry.setDurationMinutes(durationMinutes);
|
|
||||||
entry.addParticipant(entity.getUserId(), GameRole.PLAYER);
|
|
||||||
entity.getMitspieler().stream()
|
|
||||||
.filter(m -> m.getUserId() != null)
|
|
||||||
.forEach(m -> entry.addParticipant(m.getUserId(), GameRole.PLAYER));
|
|
||||||
gameHistoryRepository.save(entry);
|
|
||||||
|
|
||||||
// BDSM-XP für alle Teilnehmer gutschreiben (Minuten = XP)
|
|
||||||
int xp = (int) durationMinutes;
|
|
||||||
userRepository.findById(entity.getUserId()).ifPresent(u -> {
|
|
||||||
u.setBdsmXp(u.getBdsmXp() + xp);
|
|
||||||
userRepository.save(u);
|
|
||||||
});
|
|
||||||
entity.getMitspieler().stream()
|
|
||||||
.filter(m -> m.getUserId() != null)
|
|
||||||
.forEach(m -> userRepository.findById(m.getUserId()).ifPresent(u -> {
|
|
||||||
u.setBdsmXp(u.getBdsmXp() + xp);
|
|
||||||
userRepository.save(u);
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Eigene-Gerät-Gäste über ordentliches Spielende benachrichtigen
|
|
||||||
ORDENTLICH_BEENDET.add(sessionId);
|
ORDENTLICH_BEENDET.add(sessionId);
|
||||||
String endNachricht = "Das BDSM-Spiel wurde erfolgreich beendet. Danke fürs Mitspielen! 🎉";
|
bdsmGameService.spielAbschliessen(entity);
|
||||||
entity.getMitspieler().stream()
|
|
||||||
.filter(m -> m.isEigenesGeraet() && m.getUserId() != null)
|
|
||||||
.forEach(m -> systemMessageService.send(entity.getUserId(), m.getUserId(),
|
|
||||||
endNachricht, "/userhome.html", MessageCause.GAME_STATE));
|
|
||||||
|
|
||||||
aktiveSperreRepository.deleteAll(entity.getAktiveSperren());
|
|
||||||
mitspielerRepository.deleteAll(entity.getMitspieler());
|
|
||||||
sessionRepository.delete(entity);
|
|
||||||
return ResponseEntity.accepted().build();
|
return ResponseEntity.accepted().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,89 +511,20 @@ public class BdsmGameController {
|
|||||||
@PostMapping("/{sessionId}/zu-chastity")
|
@PostMapping("/{sessionId}/zu-chastity")
|
||||||
public ResponseEntity<Map<String, Object>> zuChastity(
|
public ResponseEntity<Map<String, Object>> zuChastity(
|
||||||
@PathVariable UUID sessionId, @RequestBody ZuChastityRequest req) {
|
@PathVariable UUID sessionId, @RequestBody ZuChastityRequest req) {
|
||||||
BdsmGameEntity entity = sessionRepository.findById(sessionId).orElse(null);
|
try {
|
||||||
if (entity == null) return ResponseEntity.notFound().build();
|
CardLockEntity newLock = bdsmGameService.zuChastity(
|
||||||
|
sessionId, req.lockId(), req.lockeeUserId(), req.keyholderUserId());
|
||||||
CardLockEntity template = cardlockRepository.findById(req.lockId()).orElse(null);
|
Map<String, Object> response = new LinkedHashMap<>();
|
||||||
if (template == null) return ResponseEntity.badRequest().build();
|
response.put("lockId", newLock.getLockId().toString());
|
||||||
|
response.put("unlockCode", newLock.getUnlockCode());
|
||||||
// Lockee darf kein aktives Chastity-Lock haben
|
return ResponseEntity.ok(response);
|
||||||
if (req.lockeeUserId() != null
|
} catch (IllegalArgumentException e) {
|
||||||
&& cardlockRepository.existsByLockeeAndStartTimeIsNotNullAndUnlockTimeIsNull(req.lockeeUserId())) {
|
String msg = e.getMessage();
|
||||||
|
if (msg != null && msg.contains("Session")) return ResponseEntity.notFound().build();
|
||||||
|
return ResponseEntity.badRequest().build();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
return ResponseEntity.status(409).build();
|
return ResponseEntity.status(409).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Neues Lock mit Template-Einstellungen für den BDSM-Lockee erstellen
|
|
||||||
LocalDateTime now = LocalDateTime.now();
|
|
||||||
CardLockEntity newLock = new CardLockEntity();
|
|
||||||
newLock.setName(template.getName());
|
|
||||||
newLock.setLockee(req.lockeeUserId());
|
|
||||||
newLock.setKeyholder(req.keyholderUserId());
|
|
||||||
newLock.setInitialCards(template.getInitialCards());
|
|
||||||
newLock.setPickEveryMinute(template.getPickEveryMinute());
|
|
||||||
newLock.setAccumulatePicks(template.isAccumulatePicks());
|
|
||||||
newLock.setShowRemainingCards(template.isShowRemainingCards());
|
|
||||||
newLock.setLatestOpeningtime(template.getLatestOpeningtime());
|
|
||||||
newLock.setHygineOpeningDurationMinutes(template.getHygineOpeningDurationMinutes());
|
|
||||||
newLock.setHygineOpeningEveryMinites(template.getHygineOpeningEveryMinites());
|
|
||||||
newLock.setTasks(template.getTasks());
|
|
||||||
newLock.setRequiresVerification(template.isRequiresVerification());
|
|
||||||
newLock.setTestLock(false);
|
|
||||||
newLock.setTaskCardMode(template.getTaskCardMode());
|
|
||||||
int codeLines = template.getUnlockCodeLines() != null ? template.getUnlockCodeLines() : 5;
|
|
||||||
newLock.setUnlockCodeLines(codeLines);
|
|
||||||
StringBuilder codeBuilder = new StringBuilder();
|
|
||||||
java.util.Random rng = new java.util.Random();
|
|
||||||
for (int i = 0; i < codeLines; i++) codeBuilder.append(rng.nextInt(10));
|
|
||||||
newLock.setUnlockCode(codeBuilder.toString());
|
|
||||||
newLock.setStartTime(now);
|
|
||||||
newLock.setAvailableCards(template.getInitialCards() != null
|
|
||||||
? new java.util.ArrayList<>(template.getInitialCards()) : new java.util.ArrayList<>());
|
|
||||||
newLock.setOpenPicks(0);
|
|
||||||
if (template.getPickEveryMinute() != null) {
|
|
||||||
newLock.setNextCardIn(now.plusMinutes(template.getPickEveryMinute()));
|
|
||||||
}
|
|
||||||
if (template.getHygineOpeningEveryMinites() != null) {
|
|
||||||
newLock.setLastHygineOpening(now);
|
|
||||||
}
|
|
||||||
cardlockRepository.save(newLock);
|
|
||||||
|
|
||||||
// Lockee benachrichtigen (falls UserAccount vorhanden)
|
|
||||||
if (req.lockeeUserId() != null) {
|
|
||||||
userRepository.findById(req.keyholderUserId()).ifPresent(keyholder ->
|
|
||||||
systemMessageService.send(req.keyholderUserId(), req.lockeeUserId(),
|
|
||||||
keyholder.getName() + " hat nach dem BDSM Game ein Chastity Lock auf dich gesetzt.",
|
|
||||||
"/activelock.html", MessageCause.GAME_STATE));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spielabschluss-Logik (wie spielAbgeschlossen, aber ohne eigenen Delete)
|
|
||||||
LocalDateTime endTime = LocalDateTime.now();
|
|
||||||
long durationMinutes = Duration.between(entity.getStartZeit(), endTime).toMinutes();
|
|
||||||
GameHistoryEntity entry = new GameHistoryEntity();
|
|
||||||
entry.setGameName("BDSM Game");
|
|
||||||
entry.setGameType(GameType.BDSM);
|
|
||||||
entry.setStartTime(entity.getStartZeit());
|
|
||||||
entry.setEndTime(endTime);
|
|
||||||
entry.setDurationMinutes(durationMinutes);
|
|
||||||
entry.addParticipant(entity.getUserId(), GameRole.PLAYER);
|
|
||||||
entity.getMitspieler().stream()
|
|
||||||
.filter(m -> m.getUserId() != null)
|
|
||||||
.forEach(m -> entry.addParticipant(m.getUserId(), GameRole.PLAYER));
|
|
||||||
gameHistoryRepository.save(entry);
|
|
||||||
|
|
||||||
int xp = (int) durationMinutes;
|
|
||||||
userRepository.findById(entity.getUserId()).ifPresent(u -> { u.setBdsmXp(u.getBdsmXp() + xp); userRepository.save(u); });
|
|
||||||
entity.getMitspieler().stream().filter(m -> m.getUserId() != null)
|
|
||||||
.forEach(m -> userRepository.findById(m.getUserId()).ifPresent(u -> { u.setBdsmXp(u.getBdsmXp() + xp); userRepository.save(u); }));
|
|
||||||
|
|
||||||
aktiveSperreRepository.deleteAll(entity.getAktiveSperren());
|
|
||||||
mitspielerRepository.deleteAll(entity.getMitspieler());
|
|
||||||
sessionRepository.delete(entity);
|
|
||||||
|
|
||||||
Map<String, Object> response = new LinkedHashMap<>();
|
|
||||||
response.put("lockId", newLock.getLockId().toString());
|
|
||||||
response.put("unlockCode", newLock.getUnlockCode());
|
|
||||||
return ResponseEntity.ok(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gibt zurück welches Werkzeug für einen User durch ein aktives Chastity-Lock blockiert ist. */
|
/** Gibt zurück welches Werkzeug für einen User durch ein aktives Chastity-Lock blockiert ist. */
|
||||||
|
|||||||
@@ -33,10 +33,15 @@ public class BdsmSetupDraftController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<Map<String, Object>> getDraft(Principal principal) {
|
public ResponseEntity<Map<String, Object>> getDraft(
|
||||||
|
@RequestParam(required = false) String setupId,
|
||||||
|
Principal principal) {
|
||||||
UUID userId = currentUserId(principal);
|
UUID userId = currentUserId(principal);
|
||||||
if (userId == null) return ResponseEntity.status(401).build();
|
if (userId == null) return ResponseEntity.status(401).build();
|
||||||
return draftRepository.findByUserId(userId)
|
var lookup = (setupId != null && !setupId.isBlank())
|
||||||
|
? draftRepository.findBySetupId(setupId)
|
||||||
|
: draftRepository.findByUserId(userId);
|
||||||
|
return lookup
|
||||||
.map(d -> {
|
.map(d -> {
|
||||||
Map<String, Object> m = new LinkedHashMap<>();
|
Map<String, Object> m = new LinkedHashMap<>();
|
||||||
m.put("setupId", d.getSetupId());
|
m.put("setupId", d.getSetupId());
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ import java.util.UUID;
|
|||||||
|
|
||||||
public interface BdsmSetupDraftRepository extends JpaRepository<BdsmSetupDraftEntity, UUID> {
|
public interface BdsmSetupDraftRepository extends JpaRepository<BdsmSetupDraftEntity, UUID> {
|
||||||
Optional<BdsmSetupDraftEntity> findByUserId(UUID userId);
|
Optional<BdsmSetupDraftEntity> findByUserId(UUID userId);
|
||||||
|
Optional<BdsmSetupDraftEntity> findBySetupId(String setupId);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
|||||||
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
|
import de.oaa.xxx.games.history.GameHistoryRepository;
|
||||||
|
import de.oaa.xxx.games.chastity.verification.VerificationRepository;
|
||||||
|
import de.oaa.xxx.games.chastity.verification.VerificationVoteRepository;
|
||||||
|
import de.oaa.xxx.user.UserRepository;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory für CardLockService-Instanzen.
|
||||||
|
*
|
||||||
|
* CardLockService hält pro Instanz den Zustand eines konkreten CardLockEntity
|
||||||
|
* und kann daher kein Singleton-Bean sein. Diese Factory zentralisiert die
|
||||||
|
* Erzeugung und verwaltet alle Abhängigkeiten als injizierte Singletons.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class CardLockServiceFactory {
|
||||||
|
|
||||||
|
private final VerificationRepository verificationRepository;
|
||||||
|
private final VerificationVoteRepository verificationVoteRepository;
|
||||||
|
private final CardLockRepository cardLockRepository;
|
||||||
|
private final GameHistoryRepository gameHistoryRepository;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
|
public CardLockServiceFactory(VerificationRepository verificationRepository,
|
||||||
|
VerificationVoteRepository verificationVoteRepository,
|
||||||
|
CardLockRepository cardLockRepository,
|
||||||
|
GameHistoryRepository gameHistoryRepository,
|
||||||
|
UserRepository userRepository) {
|
||||||
|
this.verificationRepository = verificationRepository;
|
||||||
|
this.verificationVoteRepository = verificationVoteRepository;
|
||||||
|
this.cardLockRepository = cardLockRepository;
|
||||||
|
this.gameHistoryRepository = gameHistoryRepository;
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt eine neue CardLockService-Instanz für das gegebene Lock.
|
||||||
|
*/
|
||||||
|
public CardLockService create(CardLockEntity lock) {
|
||||||
|
return new CardLockService(
|
||||||
|
lock,
|
||||||
|
verificationRepository,
|
||||||
|
verificationVoteRepository,
|
||||||
|
cardLockRepository,
|
||||||
|
gameHistoryRepository,
|
||||||
|
userRepository
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
package de.oaa.xxx.passwordreset;
|
package de.oaa.xxx.passwordreset;
|
||||||
|
|
||||||
public record PasswordResetConfirm(String token, String passwordHash) {}
|
public record PasswordResetConfirm(String token, String password) {}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -25,15 +26,18 @@ public class PasswordResetController {
|
|||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final MailService mailService;
|
private final MailService mailService;
|
||||||
private final MailTemplateService mailTemplateService;
|
private final MailTemplateService mailTemplateService;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
public PasswordResetController(PasswordResetRepository passwordResetRepository,
|
public PasswordResetController(PasswordResetRepository passwordResetRepository,
|
||||||
UserRepository userRepository,
|
UserRepository userRepository,
|
||||||
MailService mailService,
|
MailService mailService,
|
||||||
MailTemplateService mailTemplateService) {
|
MailTemplateService mailTemplateService,
|
||||||
|
PasswordEncoder passwordEncoder) {
|
||||||
this.passwordResetRepository = passwordResetRepository;
|
this.passwordResetRepository = passwordResetRepository;
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.mailService = mailService;
|
this.mailService = mailService;
|
||||||
this.mailTemplateService = mailTemplateService;
|
this.mailTemplateService = mailTemplateService;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/request")
|
@PostMapping("/request")
|
||||||
@@ -67,7 +71,7 @@ public class PasswordResetController {
|
|||||||
return ResponseEntity.badRequest().build();
|
return ResponseEntity.badRequest().build();
|
||||||
}
|
}
|
||||||
userRepository.findByEmail(entity.get().getEmail()).ifPresent(user -> {
|
userRepository.findByEmail(entity.get().getEmail()).ifPresent(user -> {
|
||||||
user.setPassword(confirm.passwordHash());
|
user.setPassword(passwordEncoder.encode(confirm.password()));
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
LOGGER.info("Passwort zurückgesetzt für: {}", entity.get().getEmail());
|
LOGGER.info("Passwort zurückgesetzt für: {}", entity.get().getEmail());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package de.oaa.xxx.registration;
|
package de.oaa.xxx.registration;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
@@ -9,36 +8,29 @@ import org.springframework.web.bind.annotation.PathVariable;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import de.oaa.xxx.user.UserController;
|
|
||||||
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/activation")
|
@RequestMapping("/activation")
|
||||||
public class ActivationController {
|
public class ActivationController {
|
||||||
|
|
||||||
private final RegistrationRepository registrationRepository;
|
private final RegistrationService registrationService;
|
||||||
private final UserController userController;
|
|
||||||
|
|
||||||
public ActivationController(RegistrationRepository registrationRepository, UserController userController) {
|
public ActivationController(RegistrationService registrationService) {
|
||||||
this.registrationRepository = registrationRepository;
|
this.registrationService = registrationService;
|
||||||
this.userController = userController;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{uuid}")
|
@GetMapping("/{uuid}")
|
||||||
public ResponseEntity<Void> activate(@PathVariable String uuid) {
|
public ResponseEntity<Void> activate(@PathVariable String uuid) {
|
||||||
RegistrationEntity registration = registrationRepository.findById(UUID.fromString(uuid)).orElse(null);
|
try {
|
||||||
if (registration != null && !Boolean.TRUE.equals(registration.getActivated())) {
|
String email = registrationService.activate(uuid);
|
||||||
ResponseEntity<Void> response = userController.userAnlegen(registration.toRegistration());
|
String redirect = "/login.html?email=" + java.net.URLEncoder.encode(email, java.nio.charset.StandardCharsets.UTF_8);
|
||||||
if (response.getStatusCode().is2xxSuccessful()) {
|
return ResponseEntity.status(302).location(URI.create(redirect)).build();
|
||||||
registration.setActivated(Boolean.TRUE);
|
} catch (IllegalStateException e) {
|
||||||
registrationRepository.save(registration);
|
// Bereits aktiviert → trotzdem zum Login weiterleiten (idempotent)
|
||||||
String redirect = "/login.html?email=" + java.net.URLEncoder.encode(registration.getEmail(), java.nio.charset.StandardCharsets.UTF_8);
|
|
||||||
return ResponseEntity.status(302).location(URI.create(redirect)).build();
|
|
||||||
} else {
|
|
||||||
return ResponseEntity.internalServerError().build();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ResponseEntity.internalServerError().build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public class Registration {
|
|||||||
private UUID id;
|
private UUID id;
|
||||||
private String name;
|
private String name;
|
||||||
private String email;
|
private String email;
|
||||||
private String passwordHash;
|
private String password;
|
||||||
private LocalDate geburtsdatum;
|
private LocalDate geburtsdatum;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import de.oaa.xxx.mail.Email;
|
|||||||
import de.oaa.xxx.mail.MailService;
|
import de.oaa.xxx.mail.MailService;
|
||||||
import de.oaa.xxx.mail.MailTemplateService;
|
import de.oaa.xxx.mail.MailTemplateService;
|
||||||
import de.oaa.xxx.user.UserRepository;
|
import de.oaa.xxx.user.UserRepository;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.Period;
|
import java.time.Period;
|
||||||
@@ -30,13 +31,16 @@ public class RegistrationController {
|
|||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final MailService mailService;
|
private final MailService mailService;
|
||||||
private final MailTemplateService mailTemplateService;
|
private final MailTemplateService mailTemplateService;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
public RegistrationController(RegistrationRepository registrationRepository, UserRepository userRepository,
|
public RegistrationController(RegistrationRepository registrationRepository, UserRepository userRepository,
|
||||||
MailService mailService, MailTemplateService mailTemplateService) {
|
MailService mailService, MailTemplateService mailTemplateService,
|
||||||
|
PasswordEncoder passwordEncoder) {
|
||||||
this.registrationRepository = registrationRepository;
|
this.registrationRepository = registrationRepository;
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.mailService = mailService;
|
this.mailService = mailService;
|
||||||
this.mailTemplateService = mailTemplateService;
|
this.mailTemplateService = mailTemplateService;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@@ -57,6 +61,8 @@ public class RegistrationController {
|
|||||||
LOGGER.warn("User mit Name {} bereits vorhanden", registration.getName());
|
LOGGER.warn("User mit Name {} bereits vorhanden", registration.getName());
|
||||||
return ResponseEntity.status(409).build();
|
return ResponseEntity.status(409).build();
|
||||||
}
|
}
|
||||||
|
// Passwort serverseitig mit BCrypt hashen
|
||||||
|
registration.setPassword(passwordEncoder.encode(registration.getPassword()));
|
||||||
RegistrationEntity entity = RegistrationEntity.create(registration);
|
RegistrationEntity entity = RegistrationEntity.create(registration);
|
||||||
registrationRepository.save(entity);
|
registrationRepository.save(entity);
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public class RegistrationEntity {
|
|||||||
registration.setId(registrationId);
|
registration.setId(registrationId);
|
||||||
registration.setEmail(email);
|
registration.setEmail(email);
|
||||||
registration.setName(name);
|
registration.setName(name);
|
||||||
registration.setPasswordHash(password);
|
registration.setPassword(password);
|
||||||
registration.setGeburtsdatum(geburtsdatum);
|
registration.setGeburtsdatum(geburtsdatum);
|
||||||
return registration;
|
return registration;
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ public class RegistrationEntity {
|
|||||||
entity.setEmail(registration.getEmail());
|
entity.setEmail(registration.getEmail());
|
||||||
entity.setActivated(Boolean.FALSE);
|
entity.setActivated(Boolean.FALSE);
|
||||||
entity.setName(registration.getName());
|
entity.setName(registration.getName());
|
||||||
entity.setPassword(registration.getPasswordHash());
|
entity.setPassword(registration.getPassword());
|
||||||
entity.setGeburtsdatum(registration.getGeburtsdatum());
|
entity.setGeburtsdatum(registration.getGeburtsdatum());
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package de.oaa.xxx.registration;
|
||||||
|
|
||||||
|
import de.oaa.xxx.user.UserService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Koordiniert den Aktivierungsflow: liest die RegistrationEntity aus der DB
|
||||||
|
* und delegiert die User-Anlage an den UserService.
|
||||||
|
* Ersetzt den direkten Controller→Controller-Aufruf im ActivationController.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class RegistrationService {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(RegistrationService.class);
|
||||||
|
|
||||||
|
private final RegistrationRepository registrationRepository;
|
||||||
|
private final UserService userService;
|
||||||
|
|
||||||
|
public RegistrationService(RegistrationRepository registrationRepository, UserService userService) {
|
||||||
|
this.registrationRepository = registrationRepository;
|
||||||
|
this.userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aktiviert eine Registrierung: legt den User an und markiert die Registration als aktiviert.
|
||||||
|
*
|
||||||
|
* @return E-Mail des aktivierten Users (für Redirect im Controller)
|
||||||
|
* @throws IllegalArgumentException wenn UUID ungültig oder Registration nicht gefunden
|
||||||
|
* @throws IllegalStateException wenn Registration bereits aktiviert
|
||||||
|
*/
|
||||||
|
public String activate(String uuid) {
|
||||||
|
UUID registrationId;
|
||||||
|
try {
|
||||||
|
registrationId = UUID.fromString(uuid);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new IllegalArgumentException("Ungültige UUID: " + uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
RegistrationEntity registration = registrationRepository.findById(registrationId)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("Registration nicht gefunden: " + uuid));
|
||||||
|
|
||||||
|
if (Boolean.TRUE.equals(registration.getActivated())) {
|
||||||
|
throw new IllegalStateException("Registration bereits aktiviert");
|
||||||
|
}
|
||||||
|
|
||||||
|
userService.createUser(registration.toRegistration());
|
||||||
|
|
||||||
|
registration.setActivated(Boolean.TRUE);
|
||||||
|
registrationRepository.save(registration);
|
||||||
|
|
||||||
|
LOGGER.info("Registration {} aktiviert, User {} angelegt", uuid, registration.getEmail());
|
||||||
|
return registration.getEmail();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,15 +7,16 @@ import org.slf4j.LoggerFactory;
|
|||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.ResponseCookie;
|
import org.springframework.http.ResponseCookie;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -24,21 +25,25 @@ public class LoginController {
|
|||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);
|
||||||
|
|
||||||
|
record LoginRequest(String email, String password) {}
|
||||||
|
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final JwtService jwtService;
|
private final JwtService jwtService;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
public LoginController(UserRepository userRepository, JwtService jwtService) {
|
public LoginController(UserRepository userRepository, JwtService jwtService, PasswordEncoder passwordEncoder) {
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.jwtService = jwtService;
|
this.jwtService = jwtService;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@PostMapping
|
||||||
public ResponseEntity<User> login(@RequestParam String email, @RequestParam String hash,
|
public ResponseEntity<User> login(@RequestBody LoginRequest request, HttpServletResponse response) {
|
||||||
HttpServletResponse response) {
|
var userOpt = userRepository.findByEmail(request.email());
|
||||||
Optional<UserEntity> user = userRepository.findByEmailAndPassword(email, hash);
|
if (userOpt.isPresent() && passwordEncoder.matches(request.password(), userOpt.get().getPassword())) {
|
||||||
if (user.isPresent()) {
|
UserEntity user = userOpt.get();
|
||||||
LOGGER.info("User erfolgreich angemeldet: {}", email);
|
LOGGER.info("User erfolgreich angemeldet: {}", request.email());
|
||||||
String token = jwtService.generateToken(user.get().getEmail(), user.get().getName());
|
String token = jwtService.generateToken(user.getEmail(), user.getName());
|
||||||
ResponseCookie cookie = ResponseCookie.from("jwt", token)
|
ResponseCookie cookie = ResponseCookie.from("jwt", token)
|
||||||
.httpOnly(true)
|
.httpOnly(true)
|
||||||
.sameSite("Strict")
|
.sameSite("Strict")
|
||||||
@@ -46,7 +51,7 @@ public class LoginController {
|
|||||||
.maxAge(Duration.ofHours(24))
|
.maxAge(Duration.ofHours(24))
|
||||||
.build();
|
.build();
|
||||||
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
|
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
|
||||||
return ResponseEntity.ok(user.get().toUser());
|
return ResponseEntity.ok(user.toUser());
|
||||||
} else {
|
} else {
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,34 +23,13 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import de.oaa.xxx.aufgaben.repository.AufgabeRepository;
|
|
||||||
import de.oaa.xxx.games.bdsm.entity.BdsmDefaultsEntity;
|
import de.oaa.xxx.games.bdsm.entity.BdsmDefaultsEntity;
|
||||||
import de.oaa.xxx.games.bdsm.repository.BdsmDefaultsRepository;
|
import de.oaa.xxx.games.bdsm.repository.BdsmDefaultsRepository;
|
||||||
import de.oaa.xxx.aufgaben.repository.AufgabenGruppeRepository;
|
|
||||||
import de.oaa.xxx.aufgaben.repository.FavoritRepository;
|
|
||||||
import de.oaa.xxx.aufgaben.repository.GruppenAboRepository;
|
|
||||||
import de.oaa.xxx.aufgaben.repository.SperreRepository;
|
|
||||||
import de.oaa.xxx.aufgaben.repository.StrafeRepository;
|
|
||||||
import de.oaa.xxx.aufgaben.repository.ToyRepository;
|
|
||||||
import de.oaa.xxx.emailchange.EmailChangeRepository;
|
|
||||||
import de.oaa.xxx.games.bdsm.entity.AktiveSperreEntity;
|
|
||||||
import de.oaa.xxx.games.bdsm.entity.MitspielerEntity;
|
|
||||||
import de.oaa.xxx.games.bdsm.repository.AktiveSperreRepository;
|
|
||||||
import de.oaa.xxx.games.bdsm.repository.BdsmGameRepository;
|
|
||||||
import de.oaa.xxx.games.bdsm.repository.MitspielerRepository;
|
|
||||||
import de.oaa.xxx.passwordreset.PasswordResetRepository;
|
|
||||||
import de.oaa.xxx.registration.Registration;
|
import de.oaa.xxx.registration.Registration;
|
||||||
import de.oaa.xxx.registration.RegistrationRepository;
|
import de.oaa.xxx.registration.RegistrationRepository;
|
||||||
import de.oaa.xxx.social.entity.MessageCause;
|
import de.oaa.xxx.social.entity.MessageCause;
|
||||||
import de.oaa.xxx.social.entity.NotificationPreferenceEntity;
|
import de.oaa.xxx.social.entity.NotificationPreferenceEntity;
|
||||||
import de.oaa.xxx.social.repository.KommentarLikeRepository;
|
|
||||||
import de.oaa.xxx.social.repository.KommentarRepository;
|
|
||||||
import de.oaa.xxx.social.repository.NotificationPreferenceRepository;
|
import de.oaa.xxx.social.repository.NotificationPreferenceRepository;
|
||||||
import de.oaa.xxx.social.repository.PinnwandEintragRepository;
|
|
||||||
import de.oaa.xxx.social.repository.PinnwandLikeRepository;
|
|
||||||
import de.oaa.xxx.social.repository.ProfileImageLikeRepository;
|
|
||||||
import de.oaa.xxx.social.repository.ProfileImageRepository;
|
|
||||||
import jakarta.transaction.Transactional;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/user")
|
@RequestMapping("/user")
|
||||||
@@ -60,71 +39,20 @@ public class UserController {
|
|||||||
|
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final RegistrationRepository registrationRepository;
|
private final RegistrationRepository registrationRepository;
|
||||||
private final AufgabenGruppeRepository aufgabenGruppeRepository;
|
|
||||||
private final AufgabeRepository aufgabeRepository;
|
|
||||||
private final StrafeRepository strafeRepository;
|
|
||||||
private final SperreRepository sperreRepository;
|
|
||||||
private final ToyRepository toyRepository;
|
|
||||||
private final FavoritRepository favoritRepository;
|
|
||||||
private final GruppenAboRepository gruppenAboRepository;
|
|
||||||
private final BdsmGameRepository sessionRepository;
|
|
||||||
private final AktiveSperreRepository aktiveSperreRepository;
|
|
||||||
private final MitspielerRepository mitspielerRepository;
|
|
||||||
private final EmailChangeRepository emailChangeRepository;
|
|
||||||
private final PasswordResetRepository passwordResetRepository;
|
|
||||||
private final ProfileImageRepository profileImageRepository;
|
|
||||||
private final ProfileImageLikeRepository profileImageLikeRepository;
|
|
||||||
private final PinnwandEintragRepository pinnwandEintragRepository;
|
|
||||||
private final PinnwandLikeRepository pinnwandLikeRepository;
|
|
||||||
private final KommentarRepository kommentarRepository;
|
|
||||||
private final KommentarLikeRepository kommentarLikeRepository;
|
|
||||||
private final NotificationPreferenceRepository notificationPreferenceRepository;
|
private final NotificationPreferenceRepository notificationPreferenceRepository;
|
||||||
private final BdsmDefaultsRepository bdsmDefaultsRepository;
|
private final BdsmDefaultsRepository bdsmDefaultsRepository;
|
||||||
|
private final UserService userService;
|
||||||
|
|
||||||
public UserController(UserRepository userRepository,
|
public UserController(UserRepository userRepository,
|
||||||
RegistrationRepository registrationRepository,
|
RegistrationRepository registrationRepository,
|
||||||
AufgabenGruppeRepository aufgabenGruppeRepository,
|
|
||||||
AufgabeRepository aufgabeRepository,
|
|
||||||
StrafeRepository strafeRepository,
|
|
||||||
SperreRepository sperreRepository,
|
|
||||||
ToyRepository toyRepository,
|
|
||||||
FavoritRepository favoritRepository,
|
|
||||||
GruppenAboRepository gruppenAboRepository,
|
|
||||||
BdsmGameRepository sessionRepository,
|
|
||||||
AktiveSperreRepository aktiveSperreRepository,
|
|
||||||
MitspielerRepository mitspielerRepository,
|
|
||||||
EmailChangeRepository emailChangeRepository,
|
|
||||||
PasswordResetRepository passwordResetRepository,
|
|
||||||
ProfileImageRepository profileImageRepository,
|
|
||||||
ProfileImageLikeRepository profileImageLikeRepository,
|
|
||||||
PinnwandEintragRepository pinnwandEintragRepository,
|
|
||||||
PinnwandLikeRepository pinnwandLikeRepository,
|
|
||||||
KommentarRepository kommentarRepository,
|
|
||||||
KommentarLikeRepository kommentarLikeRepository,
|
|
||||||
NotificationPreferenceRepository notificationPreferenceRepository,
|
NotificationPreferenceRepository notificationPreferenceRepository,
|
||||||
BdsmDefaultsRepository bdsmDefaultsRepository) {
|
BdsmDefaultsRepository bdsmDefaultsRepository,
|
||||||
|
UserService userService) {
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.registrationRepository = registrationRepository;
|
this.registrationRepository = registrationRepository;
|
||||||
this.aufgabenGruppeRepository = aufgabenGruppeRepository;
|
|
||||||
this.aufgabeRepository = aufgabeRepository;
|
|
||||||
this.strafeRepository = strafeRepository;
|
|
||||||
this.sperreRepository = sperreRepository;
|
|
||||||
this.toyRepository = toyRepository;
|
|
||||||
this.favoritRepository = favoritRepository;
|
|
||||||
this.gruppenAboRepository = gruppenAboRepository;
|
|
||||||
this.sessionRepository = sessionRepository;
|
|
||||||
this.aktiveSperreRepository = aktiveSperreRepository;
|
|
||||||
this.mitspielerRepository = mitspielerRepository;
|
|
||||||
this.emailChangeRepository = emailChangeRepository;
|
|
||||||
this.passwordResetRepository = passwordResetRepository;
|
|
||||||
this.profileImageRepository = profileImageRepository;
|
|
||||||
this.profileImageLikeRepository = profileImageLikeRepository;
|
|
||||||
this.pinnwandEintragRepository = pinnwandEintragRepository;
|
|
||||||
this.pinnwandLikeRepository = pinnwandLikeRepository;
|
|
||||||
this.kommentarRepository = kommentarRepository;
|
|
||||||
this.kommentarLikeRepository = kommentarLikeRepository;
|
|
||||||
this.notificationPreferenceRepository = notificationPreferenceRepository;
|
this.notificationPreferenceRepository = notificationPreferenceRepository;
|
||||||
this.bdsmDefaultsRepository = bdsmDefaultsRepository;
|
this.bdsmDefaultsRepository = bdsmDefaultsRepository;
|
||||||
|
this.userService = userService;
|
||||||
}
|
}
|
||||||
|
|
||||||
record ProfilePictureRequest(String picture, String pictureHq) {}
|
record ProfilePictureRequest(String picture, String pictureHq) {}
|
||||||
@@ -250,6 +178,7 @@ public class UserController {
|
|||||||
BdsmDefaultsEntity d = bdsmDefaultsRepository.findByUserId(userId)
|
BdsmDefaultsEntity d = bdsmDefaultsRepository.findByUserId(userId)
|
||||||
.orElse(new BdsmDefaultsEntity());
|
.orElse(new BdsmDefaultsEntity());
|
||||||
Map<String, Object> result = new java.util.LinkedHashMap<>();
|
Map<String, Object> result = new java.util.LinkedHashMap<>();
|
||||||
|
result.put("geschlecht", userOpt.get().getGeschlecht() != null ? userOpt.get().getGeschlecht().name() : null);
|
||||||
result.put("spieltMit", splitOrEmpty(d.getSpieltMit()));
|
result.put("spieltMit", splitOrEmpty(d.getSpieltMit()));
|
||||||
result.put("rollen", splitOrEmpty(d.getRollen()));
|
result.put("rollen", splitOrEmpty(d.getRollen()));
|
||||||
result.put("werkzeuge", splitOrEmpty(d.getWerkzeuge()));
|
result.put("werkzeuge", splitOrEmpty(d.getWerkzeuge()));
|
||||||
@@ -322,84 +251,14 @@ public class UserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/me")
|
@DeleteMapping("/me")
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<Void> deleteAccount(Principal principal) {
|
public ResponseEntity<Void> deleteAccount(Principal principal) {
|
||||||
var userOpt = userRepository.findByEmail(principal.getName());
|
var userOpt = userRepository.findByEmail(principal.getName());
|
||||||
if (userOpt.isEmpty()) return ResponseEntity.status(401).build();
|
if (userOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||||
var user = userOpt.get();
|
UUID userId = userOpt.get().getUserId();
|
||||||
UUID userId = user.getUserId();
|
String email = userOpt.get().getEmail();
|
||||||
String email = user.getEmail();
|
|
||||||
|
|
||||||
LOGGER.info("Lösche Konto für User {}", email);
|
userService.deleteAccount(userId, email);
|
||||||
|
|
||||||
// 1. Delete user's AufgabenGruppen and all their content
|
|
||||||
var gruppen = aufgabenGruppeRepository.findByUserId(userId);
|
|
||||||
if (!gruppen.isEmpty()) {
|
|
||||||
aufgabeRepository.deleteAll(aufgabeRepository.findByAufgabenGruppeIn(gruppen));
|
|
||||||
strafeRepository.deleteAll(strafeRepository.findByAufgabenGruppeIn(gruppen));
|
|
||||||
sperreRepository.deleteAll(sperreRepository.findByAufgabenGruppeIn(gruppen));
|
|
||||||
for (var gruppe : gruppen) {
|
|
||||||
gruppenAboRepository.deleteByAufgabenGruppe(gruppe);
|
|
||||||
favoritRepository.deleteByAufgabenGruppeId(gruppe.getGruppenId());
|
|
||||||
}
|
|
||||||
aufgabenGruppeRepository.deleteAll(gruppen);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Delete user's Toys (join table refs already cleared above)
|
|
||||||
toyRepository.deleteAll(toyRepository.findByUserId(userId));
|
|
||||||
|
|
||||||
// 3. Delete user's own Favoriten and Gruppenabos (to other groups)
|
|
||||||
favoritRepository.deleteAll(favoritRepository.findByUserId(userId));
|
|
||||||
gruppenAboRepository.deleteAll(gruppenAboRepository.findByUserId(userId));
|
|
||||||
|
|
||||||
// 4. Delete Session with Mitspieler and AktiveSperre
|
|
||||||
var sessionOpt = sessionRepository.findByUserId(userId);
|
|
||||||
if (sessionOpt.isPresent()) {
|
|
||||||
var session = sessionOpt.get();
|
|
||||||
List<AktiveSperreEntity> sperren = session.getAktiveSperren();
|
|
||||||
List<MitspielerEntity> mitspieler = session.getMitspieler();
|
|
||||||
aktiveSperreRepository.deleteAll(sperren);
|
|
||||||
mitspielerRepository.deleteAll(mitspieler);
|
|
||||||
sessionRepository.delete(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Delete pending tokens
|
|
||||||
emailChangeRepository.findByUserEmail(email).ifPresent(emailChangeRepository::delete);
|
|
||||||
passwordResetRepository.findByEmail(email).ifPresent(passwordResetRepository::delete);
|
|
||||||
|
|
||||||
// 5b. Delete profile images and likes
|
|
||||||
var profileImages = profileImageRepository.findByUserIdOrderByUploadedAtDesc(userId);
|
|
||||||
for (var img : profileImages) {
|
|
||||||
profileImageLikeRepository.deleteByImageId(img.getImageId());
|
|
||||||
kommentarRepository.findByTargetTypeAndTargetIdOrderByCreatedAtAsc("IMAGE", img.getImageId())
|
|
||||||
.forEach(k -> {
|
|
||||||
kommentarLikeRepository.deleteByKommentarId(k.getKommentarId());
|
|
||||||
kommentarRepository.delete(k);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
profileImageRepository.deleteAll(profileImages);
|
|
||||||
profileImageLikeRepository.deleteByUserId(userId);
|
|
||||||
|
|
||||||
// 5c. Delete pinnwand entries (authored by or on user's wall) + their likes/comments
|
|
||||||
var ownWallEntries = pinnwandEintragRepository.findByProfilUserIdOrderByCreatedAtDesc(userId);
|
|
||||||
for (var e : ownWallEntries) {
|
|
||||||
pinnwandLikeRepository.deleteByEintragId(e.getEintragId());
|
|
||||||
kommentarRepository.findByTargetTypeAndTargetIdOrderByCreatedAtAsc("PINNWAND", e.getEintragId())
|
|
||||||
.forEach(k -> {
|
|
||||||
kommentarLikeRepository.deleteByKommentarId(k.getKommentarId());
|
|
||||||
kommentarRepository.delete(k);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
pinnwandEintragRepository.deleteAll(ownWallEntries);
|
|
||||||
pinnwandEintragRepository.deleteByAuthorId(userId);
|
|
||||||
pinnwandLikeRepository.deleteByUserId(userId);
|
|
||||||
kommentarRepository.deleteByAuthorId(userId);
|
|
||||||
kommentarLikeRepository.deleteByUserId(userId);
|
|
||||||
|
|
||||||
// 6. Delete user
|
|
||||||
userRepository.delete(user);
|
|
||||||
|
|
||||||
// Clear JWT cookie
|
|
||||||
ResponseCookie cookie = ResponseCookie.from("jwt", "")
|
ResponseCookie cookie = ResponseCookie.from("jwt", "")
|
||||||
.httpOnly(true)
|
.httpOnly(true)
|
||||||
.sameSite("Strict")
|
.sameSite("Strict")
|
||||||
@@ -413,30 +272,15 @@ public class UserController {
|
|||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public ResponseEntity<Void> userAnlegen(@RequestBody Registration registration) {
|
public ResponseEntity<Void> userAnlegen(@RequestBody Registration registration) {
|
||||||
if (registration.getEmail() == null || registration.getPasswordHash() == null || registration.getName() == null) {
|
|
||||||
return ResponseEntity.badRequest().build();
|
|
||||||
}
|
|
||||||
if (userRepository.findByEmail(registration.getEmail()).isPresent()) {
|
|
||||||
LOGGER.warn("User mit E-Mail {} bereits vorhanden", registration.getEmail());
|
|
||||||
return ResponseEntity.status(409).build();
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
UserEntity entity = new UserEntity();
|
userService.createUser(registration);
|
||||||
entity.setUserId(UUID.randomUUID());
|
|
||||||
entity.setEmail(registration.getEmail());
|
|
||||||
entity.setName(registration.getName());
|
|
||||||
entity.setPassword(registration.getPasswordHash());
|
|
||||||
entity.setGeburtsdatum(registration.getGeburtsdatum());
|
|
||||||
userRepository.save(entity);
|
|
||||||
|
|
||||||
for (MessageCause cause : MessageCause.values()) {
|
|
||||||
notificationPreferenceRepository.save(
|
|
||||||
NotificationPreferenceEntity.defaultFor(entity.getUserId(), cause));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResponseEntity.status(201).build();
|
return ResponseEntity.status(201).build();
|
||||||
} catch (Exception exception) {
|
} catch (IllegalArgumentException e) {
|
||||||
LOGGER.error(exception.getMessage(), exception);
|
return ResponseEntity.badRequest().build();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
return ResponseEntity.status(409).build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error(e.getMessage(), e);
|
||||||
return ResponseEntity.internalServerError().build();
|
return ResponseEntity.internalServerError().build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import java.util.UUID;
|
|||||||
|
|
||||||
public interface UserRepository extends JpaRepository<UserEntity, UUID> {
|
public interface UserRepository extends JpaRepository<UserEntity, UUID> {
|
||||||
|
|
||||||
Optional<UserEntity> findByEmailAndPassword(String email, String password);
|
|
||||||
Optional<UserEntity> findByEmail(String email);
|
Optional<UserEntity> findByEmail(String email);
|
||||||
Optional<UserEntity> findByName(String name);
|
Optional<UserEntity> findByName(String name);
|
||||||
List<UserEntity> findByNameContainingIgnoreCase(String name);
|
List<UserEntity> findByNameContainingIgnoreCase(String name);
|
||||||
|
|||||||
210
xxxthegame/src/main/java/de/oaa/xxx/user/UserService.java
Normal file
210
xxxthegame/src/main/java/de/oaa/xxx/user/UserService.java
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
package de.oaa.xxx.user;
|
||||||
|
|
||||||
|
import de.oaa.xxx.aufgaben.repository.AufgabeRepository;
|
||||||
|
import de.oaa.xxx.aufgaben.repository.AufgabenGruppeRepository;
|
||||||
|
import de.oaa.xxx.aufgaben.repository.FavoritRepository;
|
||||||
|
import de.oaa.xxx.aufgaben.repository.GruppenAboRepository;
|
||||||
|
import de.oaa.xxx.aufgaben.repository.SperreRepository;
|
||||||
|
import de.oaa.xxx.aufgaben.repository.StrafeRepository;
|
||||||
|
import de.oaa.xxx.aufgaben.repository.ToyRepository;
|
||||||
|
import de.oaa.xxx.emailchange.EmailChangeRepository;
|
||||||
|
import de.oaa.xxx.games.bdsm.entity.AktiveSperreEntity;
|
||||||
|
import de.oaa.xxx.games.bdsm.entity.MitspielerEntity;
|
||||||
|
import de.oaa.xxx.games.bdsm.repository.AktiveSperreRepository;
|
||||||
|
import de.oaa.xxx.games.bdsm.repository.BdsmGameRepository;
|
||||||
|
import de.oaa.xxx.games.bdsm.repository.MitspielerRepository;
|
||||||
|
import de.oaa.xxx.passwordreset.PasswordResetRepository;
|
||||||
|
import de.oaa.xxx.registration.Registration;
|
||||||
|
import de.oaa.xxx.social.entity.MessageCause;
|
||||||
|
import de.oaa.xxx.social.entity.NotificationPreferenceEntity;
|
||||||
|
import de.oaa.xxx.social.repository.KommentarLikeRepository;
|
||||||
|
import de.oaa.xxx.social.repository.KommentarRepository;
|
||||||
|
import de.oaa.xxx.social.repository.NotificationPreferenceRepository;
|
||||||
|
import de.oaa.xxx.social.repository.PinnwandEintragRepository;
|
||||||
|
import de.oaa.xxx.social.repository.PinnwandLikeRepository;
|
||||||
|
import de.oaa.xxx.social.repository.ProfileImageLikeRepository;
|
||||||
|
import de.oaa.xxx.social.repository.ProfileImageRepository;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UserService {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(UserService.class);
|
||||||
|
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final AufgabenGruppeRepository aufgabenGruppeRepository;
|
||||||
|
private final AufgabeRepository aufgabeRepository;
|
||||||
|
private final StrafeRepository strafeRepository;
|
||||||
|
private final SperreRepository sperreRepository;
|
||||||
|
private final ToyRepository toyRepository;
|
||||||
|
private final FavoritRepository favoritRepository;
|
||||||
|
private final GruppenAboRepository gruppenAboRepository;
|
||||||
|
private final BdsmGameRepository sessionRepository;
|
||||||
|
private final AktiveSperreRepository aktiveSperreRepository;
|
||||||
|
private final MitspielerRepository mitspielerRepository;
|
||||||
|
private final EmailChangeRepository emailChangeRepository;
|
||||||
|
private final PasswordResetRepository passwordResetRepository;
|
||||||
|
private final ProfileImageRepository profileImageRepository;
|
||||||
|
private final ProfileImageLikeRepository profileImageLikeRepository;
|
||||||
|
private final PinnwandEintragRepository pinnwandEintragRepository;
|
||||||
|
private final PinnwandLikeRepository pinnwandLikeRepository;
|
||||||
|
private final KommentarRepository kommentarRepository;
|
||||||
|
private final KommentarLikeRepository kommentarLikeRepository;
|
||||||
|
private final NotificationPreferenceRepository notificationPreferenceRepository;
|
||||||
|
|
||||||
|
public UserService(UserRepository userRepository,
|
||||||
|
AufgabenGruppeRepository aufgabenGruppeRepository,
|
||||||
|
AufgabeRepository aufgabeRepository,
|
||||||
|
StrafeRepository strafeRepository,
|
||||||
|
SperreRepository sperreRepository,
|
||||||
|
ToyRepository toyRepository,
|
||||||
|
FavoritRepository favoritRepository,
|
||||||
|
GruppenAboRepository gruppenAboRepository,
|
||||||
|
BdsmGameRepository sessionRepository,
|
||||||
|
AktiveSperreRepository aktiveSperreRepository,
|
||||||
|
MitspielerRepository mitspielerRepository,
|
||||||
|
EmailChangeRepository emailChangeRepository,
|
||||||
|
PasswordResetRepository passwordResetRepository,
|
||||||
|
ProfileImageRepository profileImageRepository,
|
||||||
|
ProfileImageLikeRepository profileImageLikeRepository,
|
||||||
|
PinnwandEintragRepository pinnwandEintragRepository,
|
||||||
|
PinnwandLikeRepository pinnwandLikeRepository,
|
||||||
|
KommentarRepository kommentarRepository,
|
||||||
|
KommentarLikeRepository kommentarLikeRepository,
|
||||||
|
NotificationPreferenceRepository notificationPreferenceRepository) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.aufgabenGruppeRepository = aufgabenGruppeRepository;
|
||||||
|
this.aufgabeRepository = aufgabeRepository;
|
||||||
|
this.strafeRepository = strafeRepository;
|
||||||
|
this.sperreRepository = sperreRepository;
|
||||||
|
this.toyRepository = toyRepository;
|
||||||
|
this.favoritRepository = favoritRepository;
|
||||||
|
this.gruppenAboRepository = gruppenAboRepository;
|
||||||
|
this.sessionRepository = sessionRepository;
|
||||||
|
this.aktiveSperreRepository = aktiveSperreRepository;
|
||||||
|
this.mitspielerRepository = mitspielerRepository;
|
||||||
|
this.emailChangeRepository = emailChangeRepository;
|
||||||
|
this.passwordResetRepository = passwordResetRepository;
|
||||||
|
this.profileImageRepository = profileImageRepository;
|
||||||
|
this.profileImageLikeRepository = profileImageLikeRepository;
|
||||||
|
this.pinnwandEintragRepository = pinnwandEintragRepository;
|
||||||
|
this.pinnwandLikeRepository = pinnwandLikeRepository;
|
||||||
|
this.kommentarRepository = kommentarRepository;
|
||||||
|
this.kommentarLikeRepository = kommentarLikeRepository;
|
||||||
|
this.notificationPreferenceRepository = notificationPreferenceRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Löscht einen User-Account vollständig inklusive aller abhängigen Daten.
|
||||||
|
* Gibt die gelöschte E-Mail zurück (wird für Cookie-Clearing im Controller benötigt).
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public void deleteAccount(UUID userId, String email) {
|
||||||
|
var user = userRepository.findById(userId)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("User nicht gefunden: " + userId));
|
||||||
|
|
||||||
|
LOGGER.info("Lösche Konto für User {}", email);
|
||||||
|
|
||||||
|
// 1. AufgabenGruppen und deren Inhalte löschen
|
||||||
|
var gruppen = aufgabenGruppeRepository.findByUserId(userId);
|
||||||
|
if (!gruppen.isEmpty()) {
|
||||||
|
aufgabeRepository.deleteAll(aufgabeRepository.findByAufgabenGruppeIn(gruppen));
|
||||||
|
strafeRepository.deleteAll(strafeRepository.findByAufgabenGruppeIn(gruppen));
|
||||||
|
sperreRepository.deleteAll(sperreRepository.findByAufgabenGruppeIn(gruppen));
|
||||||
|
for (var gruppe : gruppen) {
|
||||||
|
gruppenAboRepository.deleteByAufgabenGruppe(gruppe);
|
||||||
|
favoritRepository.deleteByAufgabenGruppeId(gruppe.getGruppenId());
|
||||||
|
}
|
||||||
|
aufgabenGruppeRepository.deleteAll(gruppen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Toys löschen
|
||||||
|
toyRepository.deleteAll(toyRepository.findByUserId(userId));
|
||||||
|
|
||||||
|
// 3. Eigene Favoriten und Gruppenabos löschen
|
||||||
|
favoritRepository.deleteAll(favoritRepository.findByUserId(userId));
|
||||||
|
gruppenAboRepository.deleteAll(gruppenAboRepository.findByUserId(userId));
|
||||||
|
|
||||||
|
// 4. BDSM-Session mit Mitspieler und AktiveSperre löschen
|
||||||
|
var sessionOpt = sessionRepository.findByUserId(userId);
|
||||||
|
if (sessionOpt.isPresent()) {
|
||||||
|
var session = sessionOpt.get();
|
||||||
|
List<AktiveSperreEntity> sperren = session.getAktiveSperren();
|
||||||
|
List<MitspielerEntity> mitspieler = session.getMitspieler();
|
||||||
|
aktiveSperreRepository.deleteAll(sperren);
|
||||||
|
mitspielerRepository.deleteAll(mitspieler);
|
||||||
|
sessionRepository.delete(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Pending Tokens löschen
|
||||||
|
emailChangeRepository.findByUserEmail(email).ifPresent(emailChangeRepository::delete);
|
||||||
|
passwordResetRepository.findByEmail(email).ifPresent(passwordResetRepository::delete);
|
||||||
|
|
||||||
|
// 5b. Profilbilder und Likes löschen
|
||||||
|
var profileImages = profileImageRepository.findByUserIdOrderByUploadedAtDesc(userId);
|
||||||
|
for (var img : profileImages) {
|
||||||
|
profileImageLikeRepository.deleteByImageId(img.getImageId());
|
||||||
|
kommentarRepository.findByTargetTypeAndTargetIdOrderByCreatedAtAsc("IMAGE", img.getImageId())
|
||||||
|
.forEach(k -> {
|
||||||
|
kommentarLikeRepository.deleteByKommentarId(k.getKommentarId());
|
||||||
|
kommentarRepository.delete(k);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
profileImageRepository.deleteAll(profileImages);
|
||||||
|
profileImageLikeRepository.deleteByUserId(userId);
|
||||||
|
|
||||||
|
// 5c. Pinnwand-Einträge und Likes/Kommentare löschen
|
||||||
|
var ownWallEntries = pinnwandEintragRepository.findByProfilUserIdOrderByCreatedAtDesc(userId);
|
||||||
|
for (var e : ownWallEntries) {
|
||||||
|
pinnwandLikeRepository.deleteByEintragId(e.getEintragId());
|
||||||
|
kommentarRepository.findByTargetTypeAndTargetIdOrderByCreatedAtAsc("PINNWAND", e.getEintragId())
|
||||||
|
.forEach(k -> {
|
||||||
|
kommentarLikeRepository.deleteByKommentarId(k.getKommentarId());
|
||||||
|
kommentarRepository.delete(k);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pinnwandEintragRepository.deleteAll(ownWallEntries);
|
||||||
|
pinnwandEintragRepository.deleteByAuthorId(userId);
|
||||||
|
pinnwandLikeRepository.deleteByUserId(userId);
|
||||||
|
kommentarRepository.deleteByAuthorId(userId);
|
||||||
|
kommentarLikeRepository.deleteByUserId(userId);
|
||||||
|
|
||||||
|
// 6. User löschen
|
||||||
|
userRepository.delete(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legt einen neuen User aus einer bestätigten Registration an
|
||||||
|
* und erstellt die Standard-Benachrichtigungseinstellungen.
|
||||||
|
*/
|
||||||
|
public void createUser(Registration registration) {
|
||||||
|
if (registration.getEmail() == null || registration.getPassword() == null || registration.getName() == null) {
|
||||||
|
throw new IllegalArgumentException("E-Mail, Passwort und Name sind Pflichtfelder");
|
||||||
|
}
|
||||||
|
if (userRepository.findByEmail(registration.getEmail()).isPresent()) {
|
||||||
|
LOGGER.warn("User mit E-Mail {} bereits vorhanden", registration.getEmail());
|
||||||
|
throw new IllegalStateException("E-Mail bereits vorhanden");
|
||||||
|
}
|
||||||
|
|
||||||
|
UserEntity entity = new UserEntity();
|
||||||
|
entity.setUserId(UUID.randomUUID());
|
||||||
|
entity.setEmail(registration.getEmail());
|
||||||
|
entity.setName(registration.getName());
|
||||||
|
entity.setPassword(registration.getPassword());
|
||||||
|
entity.setGeburtsdatum(registration.getGeburtsdatum());
|
||||||
|
userRepository.save(entity);
|
||||||
|
|
||||||
|
for (MessageCause cause : MessageCause.values()) {
|
||||||
|
notificationPreferenceRepository.save(
|
||||||
|
NotificationPreferenceEntity.defaultFor(entity.getUserId(), cause));
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("User {} angelegt", entity.getUserId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
document.getElementById('sub').textContent = 'Du hast die Einladung abgelehnt.';
|
document.getElementById('sub').textContent = 'Du hast die Einladung abgelehnt.';
|
||||||
document.getElementById('actions').innerHTML = '<button onclick="window.location.href=\'/userhome.html\'">Zur Startseite</button>';
|
document.getElementById('actions').innerHTML = '<button onclick="window.location.href=\'/userhome.html\'">Zur Startseite</button>';
|
||||||
} else if (mode === 'OWN_DEVICE') {
|
} else if (mode === 'OWN_DEVICE') {
|
||||||
window.location.replace(`/bdsmwarten.html?id=${einladungId}`);
|
window.location.replace(`/neubdsm.html`);
|
||||||
} else {
|
} else {
|
||||||
zeigeBestaetigt();
|
zeigeBestaetigt();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,356 +2,10 @@
|
|||||||
<html lang="de">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
<meta http-equiv="refresh" content="0;url=/neubdsm.html">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<title>BDSM Game</title>
|
||||||
<title>BDSM Game – Neue Session – XXX The Game</title>
|
|
||||||
<link rel="stylesheet" href="/css/variables.css">
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
<style>
|
|
||||||
.session-setup { }
|
|
||||||
|
|
||||||
.setup-section { margin-bottom: 2.5rem; }
|
|
||||||
.setup-section h2 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
border-bottom: 1px solid var(--color-secondary);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-row { margin-bottom: 1.25rem; }
|
|
||||||
.setting-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: baseline;
|
|
||||||
margin-bottom: 0.4rem;
|
|
||||||
}
|
|
||||||
.setting-header label {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: #aaa;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.setting-value {
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-primary);
|
|
||||||
min-width: 3.5rem;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
input[type="range"] {
|
|
||||||
width: 100%;
|
|
||||||
accent-color: var(--color-primary);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-overlay {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.75);
|
|
||||||
z-index: 1000;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
.modal-card {
|
|
||||||
background: var(--color-card);
|
|
||||||
border: 1px solid var(--color-secondary);
|
|
||||||
border-radius: 14px;
|
|
||||||
padding: 2rem;
|
|
||||||
max-width: 420px;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.modal-title {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--color-text);
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
.modal-text {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: var(--color-muted);
|
|
||||||
line-height: 1.6;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
.modal-actions { display: flex; flex-direction: column; gap: 0.6rem; }
|
|
||||||
.modal-actions button { width: 100%; padding: 0.75rem; }
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="app">
|
<body>
|
||||||
|
<script>window.location.replace('/neubdsm.html');</script>
|
||||||
<div class="modal-overlay" id="modal" style="display:none;">
|
|
||||||
<div class="modal-card">
|
|
||||||
<div class="modal-title" id="modalTitle"></div>
|
|
||||||
<div class="modal-text" id="modalText"></div>
|
|
||||||
<div class="modal-actions" id="modalActions"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="main">
|
|
||||||
<div class="content session-setup">
|
|
||||||
|
|
||||||
<h1>BDSM Game</h1>
|
|
||||||
<p style="margin-bottom:2rem;">Schritt 1 von 4 – Session-Einstellungen</p>
|
|
||||||
|
|
||||||
<div class="setup-section">
|
|
||||||
<h2>Session-Einstellungen</h2>
|
|
||||||
|
|
||||||
<div class="setting-row">
|
|
||||||
<div class="setting-header">
|
|
||||||
<label for="sldStrafe">Wahrscheinlichkeit Strafe</label>
|
|
||||||
<span class="setting-value"><span id="valStrafe">15</span> %</span>
|
|
||||||
</div>
|
|
||||||
<input type="range" id="sldStrafe" min="0" max="100" value="15"
|
|
||||||
oninput="document.getElementById('valStrafe').textContent=this.value; updateWarnung()">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="setting-row">
|
|
||||||
<div class="setting-header">
|
|
||||||
<label for="sldZeitstrafe">Wahrscheinlichkeit Zeitstrafe</label>
|
|
||||||
<span class="setting-value"><span id="valZeitstrafe">15</span> %</span>
|
|
||||||
</div>
|
|
||||||
<input type="range" id="sldZeitstrafe" min="0" max="100" value="15"
|
|
||||||
oninput="document.getElementById('valZeitstrafe').textContent=this.value; updateWarnung()">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="message" id="wahrschWarnung" style="display:none; margin-top:0.75rem;"></div>
|
|
||||||
|
|
||||||
<div class="setting-row">
|
|
||||||
<div class="setting-header">
|
|
||||||
<label for="sldAufgaben">Aufgaben pro Level</label>
|
|
||||||
<span class="setting-value" id="valAufgaben">5</span>
|
|
||||||
</div>
|
|
||||||
<input type="range" id="sldAufgaben" min="1" max="20" value="5"
|
|
||||||
oninput="document.getElementById('valAufgaben').textContent=this.value">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="setting-row">
|
|
||||||
<div class="setting-header">
|
|
||||||
<label for="sldZeit">Zeitfaktor Zeitstrafen</label>
|
|
||||||
<span class="setting-value" id="valZeit">1,0</span>
|
|
||||||
</div>
|
|
||||||
<input type="range" id="sldZeit" min="5" max="20" value="10"
|
|
||||||
oninput="document.getElementById('valZeit').textContent=(this.value/10).toFixed(1).replace('.',',')">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="message" id="message"></div>
|
|
||||||
<button class="full-width" onclick="weiter()">Weiter</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/js/sidebar.js"></script>
|
|
||||||
<script>
|
|
||||||
function updateWarnung() {
|
|
||||||
const strafe = parseInt(document.getElementById('sldStrafe').value);
|
|
||||||
const zeitstrafe = parseInt(document.getElementById('sldZeitstrafe').value);
|
|
||||||
const summe = strafe + zeitstrafe;
|
|
||||||
const el = document.getElementById('wahrschWarnung');
|
|
||||||
if (summe > 98) {
|
|
||||||
el.textContent = `Kombiniert ${summe} % – Werte über 98 % sind nicht möglich.`;
|
|
||||||
el.className = 'message error';
|
|
||||||
el.style.display = 'block';
|
|
||||||
} else if (summe > 60) {
|
|
||||||
el.textContent = `Hinweis: Bei ${summe} % kombinierten Wahrscheinlichkeiten ist die Chance auf Vanilla-Aufgaben sehr gering.`;
|
|
||||||
el.className = 'message warning';
|
|
||||||
el.style.display = 'block';
|
|
||||||
} else {
|
|
||||||
el.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function weiter() {
|
|
||||||
hideMessage();
|
|
||||||
const strafe = parseInt(document.getElementById('sldStrafe').value);
|
|
||||||
const zeitstrafe = parseInt(document.getElementById('sldZeitstrafe').value);
|
|
||||||
if (strafe + zeitstrafe > 98) {
|
|
||||||
showMessage('Die kombinierten Wahrscheinlichkeiten dürfen 98 % nicht überschreiten.', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const settings = {
|
|
||||||
wahrscheinlichkeitStrafe: strafe,
|
|
||||||
wahrscheinlichkeitSperre: zeitstrafe,
|
|
||||||
aufgabenProLevel: parseInt(document.getElementById('sldAufgaben').value),
|
|
||||||
zeitfaktorZeitstrafen: parseInt(document.getElementById('sldZeit').value) / 10,
|
|
||||||
};
|
|
||||||
// Immer neue Setup-ID → Mitspieler-Konfig und Einladungen gehören zur neuen Runde
|
|
||||||
const newSetupId = crypto.randomUUID();
|
|
||||||
sessionStorage.setItem('bdsm-setup-id', newSetupId);
|
|
||||||
sessionStorage.setItem('bdsm-session-settings', JSON.stringify(settings));
|
|
||||||
sessionStorage.removeItem('bdsm-session-setup');
|
|
||||||
sessionStorage.removeItem('bdsm-session-gruppen');
|
|
||||||
sessionStorage.removeItem('bdsm-session-toys');
|
|
||||||
// Alten Draft löschen, neuen mit frischer Setup-ID anlegen
|
|
||||||
try { await fetch('/bdsm/setup-draft', { method: 'DELETE' }); } catch (_) {}
|
|
||||||
fetch('/bdsm/setup-draft', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ setupId: newSetupId, settingsJson: JSON.stringify(settings) }),
|
|
||||||
}).catch(() => {});
|
|
||||||
window.location.href = '/bdsmplayers.html';
|
|
||||||
}
|
|
||||||
|
|
||||||
function showMessage(text, type) {
|
|
||||||
const el = document.getElementById('message');
|
|
||||||
el.textContent = text;
|
|
||||||
el.className = `message ${type}`;
|
|
||||||
el.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideMessage() {
|
|
||||||
document.getElementById('message').style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Aktive-Session-Check ──
|
|
||||||
function zeigeModal(title, text, actions) {
|
|
||||||
document.getElementById('modalTitle').textContent = title;
|
|
||||||
const textEl = document.getElementById('modalText');
|
|
||||||
textEl.textContent = text;
|
|
||||||
textEl.style.display = text ? '' : 'none';
|
|
||||||
const actEl = document.getElementById('modalActions');
|
|
||||||
actEl.innerHTML = '';
|
|
||||||
actions.forEach(a => {
|
|
||||||
const btn = document.createElement('button');
|
|
||||||
btn.textContent = a.label;
|
|
||||||
btn.className = a.primary ? 'full-width' : 'full-width secondary';
|
|
||||||
btn.onclick = () => a.onClick();
|
|
||||||
actEl.appendChild(btn);
|
|
||||||
});
|
|
||||||
document.getElementById('modal').style.display = 'flex';
|
|
||||||
}
|
|
||||||
|
|
||||||
function versteckeModal() {
|
|
||||||
document.getElementById('modal').style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
const BDSM_STORAGE_KEYS = [
|
|
||||||
'bdsm-session-id', 'bdsm-session-settings', 'bdsm-session-setup',
|
|
||||||
'bdsm-session-gruppen', 'bdsm-session-toys', 'bdsm-session-game',
|
|
||||||
];
|
|
||||||
|
|
||||||
function sessionFortfahren(sid) {
|
|
||||||
BDSM_STORAGE_KEYS.forEach(k => sessionStorage.removeItem(k));
|
|
||||||
sessionStorage.setItem('bdsm-session-id', sid);
|
|
||||||
window.location.href = '/bdsmingame.html';
|
|
||||||
}
|
|
||||||
|
|
||||||
function sessionBeendenFragen(sid) {
|
|
||||||
zeigeModal(
|
|
||||||
'Session wirklich beenden?',
|
|
||||||
'Die Session und alle aktiven Sperren werden gelöscht.',
|
|
||||||
[
|
|
||||||
{ label: 'Ja, beenden', primary: true, onClick: () => sessionLoeschen(sid) },
|
|
||||||
{ label: 'Nein, fortfahren', onClick: () => sessionFortfahren(sid) },
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sessionLoeschen(sid) {
|
|
||||||
versteckeModal();
|
|
||||||
try {
|
|
||||||
await fetch('/bdsm', {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ sessionId: sid }),
|
|
||||||
});
|
|
||||||
} catch (_) { /* ignorieren */ }
|
|
||||||
BDSM_STORAGE_KEYS.forEach(k => sessionStorage.removeItem(k));
|
|
||||||
fetch('/bdsm/setup-draft', { method: 'DELETE' }).catch(() => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
(async function checkAktiveSession() {
|
|
||||||
try {
|
|
||||||
const meRes = await fetch('/login/me');
|
|
||||||
if (!meRes.ok) return;
|
|
||||||
const user = await meRes.json();
|
|
||||||
|
|
||||||
// 1. Prüfen ob User selbst eine aktive Host-Session hat
|
|
||||||
const sessionRes = await fetch(`/bdsm?userId=${user.userId}`);
|
|
||||||
if (sessionRes.ok) {
|
|
||||||
const session = await sessionRes.json();
|
|
||||||
zeigeModal(
|
|
||||||
'Aktive Session vorhanden',
|
|
||||||
'Du hast noch eine laufende Session. Möchtest du fortfahren?',
|
|
||||||
[
|
|
||||||
{ label: 'Ja, fortfahren', primary: true, onClick: () => sessionFortfahren(session.sessionId) },
|
|
||||||
{ label: 'Nein', onClick: () => sessionBeendenFragen(session.sessionId) },
|
|
||||||
]
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Prüfen ob User als Mitspieler (ACCEPTED_OWN) eingeladen wurde
|
|
||||||
const einladungRes = await fetch('/bdsm/einladung/meine-aktive');
|
|
||||||
if (!einladungRes.ok) return;
|
|
||||||
const einladung = await einladungRes.json();
|
|
||||||
|
|
||||||
if (!einladung.sessionId) {
|
|
||||||
// Spiel noch nicht gestartet → Warteseite
|
|
||||||
window.location.replace(`/bdsmwarten.html?id=${einladung.einladungId}`);
|
|
||||||
} else {
|
|
||||||
// Spiel läuft bereits → direkt als Gast rein
|
|
||||||
const mRes = await fetch(`/bdsm/${einladung.sessionId}/mitspieler/me`);
|
|
||||||
if (mRes.ok) {
|
|
||||||
const mData = await mRes.json();
|
|
||||||
sessionStorage.setItem('bdsm-guest-mitspieler-id', mData.mitspielerId);
|
|
||||||
sessionStorage.setItem('bdsm-guest-name', mData.name);
|
|
||||||
}
|
|
||||||
sessionStorage.setItem('bdsm-session-id', einladung.sessionId);
|
|
||||||
sessionStorage.setItem('bdsm-is-guest', 'true');
|
|
||||||
window.location.replace('/bdsmingame.html');
|
|
||||||
}
|
|
||||||
} catch (_) { /* ignorieren */ }
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Gespeicherte Einstellungen wiederherstellen
|
|
||||||
function applySettings(s) {
|
|
||||||
function setSlider(id, displayId, value, transform) {
|
|
||||||
const el = document.getElementById(id);
|
|
||||||
if (!el) return;
|
|
||||||
el.value = value;
|
|
||||||
document.getElementById(displayId).textContent = transform ? transform(value) : value;
|
|
||||||
}
|
|
||||||
setSlider('sldStrafe', 'valStrafe', s.wahrscheinlichkeitStrafe);
|
|
||||||
setSlider('sldZeitstrafe', 'valZeitstrafe', s.wahrscheinlichkeitSperre);
|
|
||||||
setSlider('sldAufgaben', 'valAufgaben', s.aufgabenProLevel);
|
|
||||||
setSlider('sldZeit', 'valZeit', Math.round(s.zeitfaktorZeitstrafen * 10),
|
|
||||||
v => (v / 10).toFixed(1).replace('.', ','));
|
|
||||||
updateWarnung();
|
|
||||||
}
|
|
||||||
|
|
||||||
(async function restore() {
|
|
||||||
const saved = sessionStorage.getItem('bdsm-session-settings');
|
|
||||||
if (saved) {
|
|
||||||
applySettings(JSON.parse(saved));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Fallback: Draft aus DB laden
|
|
||||||
try {
|
|
||||||
const res = await fetch('/bdsm/setup-draft');
|
|
||||||
if (!res.ok) return;
|
|
||||||
const draft = await res.json();
|
|
||||||
if (draft.setupId && !sessionStorage.getItem('bdsm-setup-id')) {
|
|
||||||
sessionStorage.setItem('bdsm-setup-id', draft.setupId);
|
|
||||||
}
|
|
||||||
if (draft.settingsJson) {
|
|
||||||
const s = JSON.parse(draft.settingsJson);
|
|
||||||
sessionStorage.setItem('bdsm-session-settings', JSON.stringify(s));
|
|
||||||
applySettings(s);
|
|
||||||
}
|
|
||||||
if (draft.setupJson) {
|
|
||||||
sessionStorage.setItem('bdsm-session-setup', draft.setupJson);
|
|
||||||
}
|
|
||||||
if (draft.gruppenJson) {
|
|
||||||
sessionStorage.setItem('bdsm-session-gruppen', draft.gruppenJson);
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -337,7 +337,7 @@
|
|||||||
const setup = JSON.parse(sessionStorage.getItem('bdsm-session-setup') || 'null');
|
const setup = JSON.parse(sessionStorage.getItem('bdsm-session-setup') || 'null');
|
||||||
const toys = JSON.parse(sessionStorage.getItem('bdsm-session-toys') || '[]');
|
const toys = JSON.parse(sessionStorage.getItem('bdsm-session-toys') || '[]');
|
||||||
const sessionId = sessionStorage.getItem('bdsm-session-id');
|
const sessionId = sessionStorage.getItem('bdsm-session-id');
|
||||||
if (!sessionId) window.location.replace('/bdsm.html');
|
if (!sessionId) window.location.replace('/neubdsm.html');
|
||||||
|
|
||||||
// Multi-Device: bin ich Gast?
|
// Multi-Device: bin ich Gast?
|
||||||
const isGuest = sessionStorage.getItem('bdsm-is-guest') === 'true';
|
const isGuest = sessionStorage.getItem('bdsm-is-guest') === 'true';
|
||||||
@@ -424,7 +424,7 @@
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(`/bdsm/${sessionId}/aufgaben/next`);
|
const res = await fetch(`/bdsm/${sessionId}/aufgaben/next`);
|
||||||
if (res.status === 204) { zeigeFinaleDialog(); return; }
|
if (res.status === 204) { zeigeFinaleDialog(); return; }
|
||||||
if (res.status === 400) { window.location.replace('/bdsmtoys.html'); return; }
|
if (res.status === 400) { window.location.replace('/neubdsm.html'); return; }
|
||||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
currentTask = await res.json();
|
currentTask = await res.json();
|
||||||
await saveAktiveAufgabe(currentTask, null);
|
await saveAktiveAufgabe(currentTask, null);
|
||||||
|
|||||||
@@ -2,984 +2,10 @@
|
|||||||
<html lang="de">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
<meta http-equiv="refresh" content="0;url=/neubdsm.html">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<title>BDSM Game</title>
|
||||||
<title>BDSM Game – Mitspieler – XXX The Game</title>
|
|
||||||
<link rel="stylesheet" href="/css/variables.css">
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
<style>
|
|
||||||
.session-setup { }
|
|
||||||
|
|
||||||
.setup-section { margin-bottom: 2.5rem; }
|
|
||||||
.setup-section h2 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
border-bottom: 1px solid var(--color-secondary);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Player cards ── */
|
|
||||||
.player-card {
|
|
||||||
background: var(--color-card);
|
|
||||||
border: 1px solid var(--color-secondary);
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 1.25rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
.player-card-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.6rem;
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
}
|
|
||||||
.player-title {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-text);
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
.player-badge {
|
|
||||||
background: var(--color-primary);
|
|
||||||
color: #fff;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
padding: 0.1rem 0.5rem;
|
|
||||||
border-radius: 10px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.player-badge-pending {
|
|
||||||
background: var(--color-secondary);
|
|
||||||
color: var(--color-muted);
|
|
||||||
font-size: 0.7rem;
|
|
||||||
padding: 0.1rem 0.5rem;
|
|
||||||
border-radius: 10px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.player-badge-accepted {
|
|
||||||
background: #1a5c2a;
|
|
||||||
color: #6fcf97;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
padding: 0.1rem 0.5rem;
|
|
||||||
border-radius: 10px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.player-remove {
|
|
||||||
margin-left: auto;
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid var(--color-secondary);
|
|
||||||
color: var(--color-muted);
|
|
||||||
padding: 0.25rem 0.6rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: normal;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color 0.15s, color 0.15s;
|
|
||||||
}
|
|
||||||
.player-remove:hover {
|
|
||||||
border-color: var(--color-primary);
|
|
||||||
color: var(--color-primary);
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
.btn-invite {
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid var(--color-primary);
|
|
||||||
color: var(--color-primary);
|
|
||||||
padding: 0.45rem 1rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 600;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.15s, color 0.15s;
|
|
||||||
}
|
|
||||||
.btn-invite:hover { background: var(--color-primary); color: #fff; }
|
|
||||||
.btn-cancel-invite {
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid var(--color-secondary);
|
|
||||||
color: var(--color-muted);
|
|
||||||
padding: 0.2rem 0.6rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: normal;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.btn-cancel-invite:hover { border-color: var(--color-primary); color: var(--color-primary); background: transparent; }
|
|
||||||
|
|
||||||
.pending-info {
|
|
||||||
text-align: center;
|
|
||||||
color: var(--color-muted);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
padding: 1.5rem 0;
|
|
||||||
}
|
|
||||||
.pending-name {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-text);
|
|
||||||
font-size: 1rem;
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
.pending-mode {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: var(--color-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-field { margin-bottom: 1rem; }
|
|
||||||
.card-field > label {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: #aaa;
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.check-group { display: flex; flex-wrap: wrap; gap: 0.5rem; }
|
|
||||||
.check-group--two-col { display: grid; grid-template-columns: 1fr 1fr; }
|
|
||||||
.check-item {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 0.45rem;
|
|
||||||
background: var(--color-secondary);
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 0.4rem 0.7rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color 0.15s;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
.check-item.is-checked { border-color: var(--color-primary); }
|
|
||||||
.check-item input { accent-color: var(--color-primary); width: auto; margin-top: 0.15rem; cursor: pointer; flex-shrink: 0; }
|
|
||||||
.check-item-label { font-size: 0.88rem; color: var(--color-text); line-height: 1.3; }
|
|
||||||
.check-item-desc { display: block; font-size: 0.72rem; color: var(--color-muted); margin-top: 0.1rem; }
|
|
||||||
|
|
||||||
.add-player-btn {
|
|
||||||
width: 100%;
|
|
||||||
background: transparent;
|
|
||||||
border: 1px dashed var(--color-secondary);
|
|
||||||
color: var(--color-muted);
|
|
||||||
padding: 0.75rem;
|
|
||||||
border-radius: 10px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: normal;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color 0.15s, color 0.15s;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
.add-player-btn:hover { border-color: var(--color-primary); color: var(--color-text); background: transparent; }
|
|
||||||
|
|
||||||
.field-error { font-size: 0.78rem; color: var(--color-primary); margin-top: 0.3rem; display: none; }
|
|
||||||
|
|
||||||
/* ── Freunde-Modal ── */
|
|
||||||
.modal-overlay {
|
|
||||||
position: fixed; inset: 0;
|
|
||||||
background: rgba(0,0,0,0.75);
|
|
||||||
z-index: 1000;
|
|
||||||
display: flex; align-items: center; justify-content: center;
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
.modal-card {
|
|
||||||
background: var(--color-card);
|
|
||||||
border: 1px solid var(--color-secondary);
|
|
||||||
border-radius: 14px;
|
|
||||||
padding: 1.75rem;
|
|
||||||
max-width: 420px; width: 100%;
|
|
||||||
}
|
|
||||||
.modal-title { font-size: 1rem; font-weight: 700; margin-bottom: 1rem; }
|
|
||||||
.check-item.is-disabled { opacity: 0.5; pointer-events: none; cursor: default; }
|
|
||||||
.friend-avatar { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; background: var(--color-secondary); flex-shrink: 0; }
|
|
||||||
.friend-combobox { position: relative; }
|
|
||||||
.friend-dropdown {
|
|
||||||
display: none; position: absolute; top: 100%; left: 0; right: 0;
|
|
||||||
background: var(--color-card); border: 1px solid var(--color-secondary);
|
|
||||||
border-radius: 8px; max-height: 220px; overflow-y: auto; z-index: 10; margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
.friend-dropdown-item {
|
|
||||||
display: flex; align-items: center; gap: 0.75rem;
|
|
||||||
padding: 0.6rem 0.75rem; cursor: pointer; transition: background 0.1s;
|
|
||||||
font-size: 0.9rem; font-weight: 600;
|
|
||||||
}
|
|
||||||
.friend-dropdown-item:hover { background: var(--color-secondary); }
|
|
||||||
.selected-friend-box {
|
|
||||||
display: none; margin-top: 0.75rem; padding: 0.6rem 0.75rem;
|
|
||||||
background: var(--color-secondary); border-radius: 8px;
|
|
||||||
font-size: 0.9rem; font-weight: 600;
|
|
||||||
border: 1px solid var(--color-primary); color: var(--color-text);
|
|
||||||
}
|
|
||||||
.modal-cancel { margin-top: 0.6rem; width: 100%; }
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="app">
|
<body>
|
||||||
|
<script>window.location.replace('/neubdsm.html');</script>
|
||||||
<div class="modal-overlay" id="errorModal" style="display:none;">
|
|
||||||
<div class="modal-card" style="text-align:center;">
|
|
||||||
<div style="font-size:1.5rem;margin-bottom:0.75rem;">⚠️</div>
|
|
||||||
<div class="modal-title" id="errorModalTitle"></div>
|
|
||||||
<div class="modal-text" id="errorModalText" style="font-size:0.9rem;color:var(--color-muted);line-height:1.5;margin-bottom:1.25rem;"></div>
|
|
||||||
<button onclick="document.getElementById('errorModal').style.display='none'">OK</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-overlay" id="friendModal" style="display:none;">
|
|
||||||
<div class="modal-card">
|
|
||||||
<div class="modal-title">Freund einladen</div>
|
|
||||||
<div class="friend-combobox">
|
|
||||||
<input type="text" id="friendSearch" placeholder="Name eingeben…" autocomplete="off" oninput="filterFreunde(this.value)">
|
|
||||||
<div class="friend-dropdown" id="friendDropdown"></div>
|
|
||||||
</div>
|
|
||||||
<div class="selected-friend-box" id="selectedFriendBox"></div>
|
|
||||||
<button id="btnEinladen" style="margin-top:1rem; width:100%;" disabled onclick="confirmedEinladen()">Einladen</button>
|
|
||||||
<button class="secondary modal-cancel" onclick="schliesseFriendModal()">Abbrechen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="main">
|
|
||||||
<div class="content session-setup">
|
|
||||||
|
|
||||||
<h1>BDSM Game</h1>
|
|
||||||
<p style="margin-bottom:2rem;">Schritt 2 von 4 – Mitspieler</p>
|
|
||||||
|
|
||||||
<div class="setup-section">
|
|
||||||
<h2>Mitspieler</h2>
|
|
||||||
<div id="playersContainer"></div>
|
|
||||||
<button class="add-player-btn" onclick="addPlayer()">+ Spieler hinzufügen</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="message" id="message"></div>
|
|
||||||
<div style="display:flex; gap:1rem;">
|
|
||||||
<button style="flex:1;" class="secondary" onclick="window.location.href='/bdsm.html'">← Zurück</button>
|
|
||||||
<button style="flex:2;" id="weiterBtn" onclick="weiter()">Weiter</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/js/sidebar.js"></script>
|
|
||||||
<script>
|
|
||||||
// SetupId erzeugen (persistent über sessionStorage)
|
|
||||||
if (!sessionStorage.getItem('bdsm-setup-id')) {
|
|
||||||
sessionStorage.setItem('bdsm-setup-id', crypto.randomUUID());
|
|
||||||
}
|
|
||||||
let setupId = sessionStorage.getItem('bdsm-setup-id');
|
|
||||||
|
|
||||||
// Draft aus DB laden wenn sessionStorage leer
|
|
||||||
async function ladeSessionOderDraft() {
|
|
||||||
if (sessionStorage.getItem('bdsm-session-settings')) return true;
|
|
||||||
try {
|
|
||||||
const res = await fetch('/bdsm/setup-draft');
|
|
||||||
if (!res.ok) { window.location.replace('/bdsm.html'); return false; }
|
|
||||||
const draft = await res.json();
|
|
||||||
if (draft.setupId) {
|
|
||||||
sessionStorage.setItem('bdsm-setup-id', draft.setupId);
|
|
||||||
setupId = draft.setupId;
|
|
||||||
}
|
|
||||||
if (draft.settingsJson) sessionStorage.setItem('bdsm-session-settings', draft.settingsJson);
|
|
||||||
if (draft.setupJson) sessionStorage.setItem('bdsm-session-setup', draft.setupJson);
|
|
||||||
if (draft.gruppenJson) sessionStorage.setItem('bdsm-session-gruppen', draft.gruppenJson);
|
|
||||||
if (!draft.settingsJson) { window.location.replace('/bdsm.html'); return false; }
|
|
||||||
return true;
|
|
||||||
} catch (_) {
|
|
||||||
window.location.replace('/bdsm.html');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const GESCHLECHTER = [
|
|
||||||
{ value: 'MAENNLICH', label: 'Männlich' },
|
|
||||||
{ value: 'WEIBLICH', label: 'Weiblich' },
|
|
||||||
{ value: 'DIVERS', label: 'Divers' },
|
|
||||||
];
|
|
||||||
const ROLLEN = [
|
|
||||||
{ value: 'AUFGABE_AKTIV', label: 'Aufgabe – Aktiv' },
|
|
||||||
{ value: 'AUFGABE_PASSIV', label: 'Aufgabe – Passiv' },
|
|
||||||
{ value: 'BESTRAFUNG_AKTIV', label: 'Bestrafung – Aktiv' },
|
|
||||||
{ value: 'BESTRAFUNG_PASSIV', label: 'Bestrafung – Passiv' },
|
|
||||||
];
|
|
||||||
const WERKZEUGE_DEFAULTS = {
|
|
||||||
MAENNLICH: ['MUND', 'PENIS', 'ANUS', 'UMSCHNALLDILDO'],
|
|
||||||
WEIBLICH: ['MUND', 'VAGINA', 'ANUS', 'UMSCHNALLDILDO'],
|
|
||||||
DIVERS: ['MUND', 'ANUS', 'UMSCHNALLDILDO'],
|
|
||||||
};
|
|
||||||
const WERKZEUGE = [
|
|
||||||
{ value: 'MUND', label: 'Mund', desc: 'Gewillt den Mund einzusetzen' },
|
|
||||||
{ value: 'VAGINA', label: 'Vagina', desc: 'Verfügt über eine Vagina und setzt sie ein' },
|
|
||||||
{ value: 'PENIS', label: 'Penis', desc: 'Verfügt über einen Penis und setzt ihn ein' },
|
|
||||||
{ value: 'ANUS', label: 'Anus', desc: 'Gewillt den Anus einzusetzen' },
|
|
||||||
{ value: 'UMSCHNALLDILDO', label: 'Umschnall-Dildo', desc: 'Verfügt über einen Umschnall-Dildo' },
|
|
||||||
];
|
|
||||||
const ROLE_LABELS = {
|
|
||||||
AUFGABE_AKTIV: 'Aufgabe – Aktiv', AUFGABE_PASSIV: 'Aufgabe – Passiv',
|
|
||||||
BESTRAFUNG_AKTIV: 'Bestrafung – Aktiv', BESTRAFUNG_PASSIV: 'Bestrafung – Passiv',
|
|
||||||
};
|
|
||||||
|
|
||||||
let playerSeq = 0;
|
|
||||||
let playerIds = [];
|
|
||||||
// { [playerId]: { einladungId, status, inviteeId, inviteeName, mode } | null }
|
|
||||||
let playerInvitations = {};
|
|
||||||
let pollIntervalId = null;
|
|
||||||
let myUserId = null;
|
|
||||||
let selfPlayerId = null;
|
|
||||||
let freundeListe = [];
|
|
||||||
|
|
||||||
function buildCheckItems(name, items, type, disabled = false) {
|
|
||||||
return items.map(({ value, label, desc }) => `
|
|
||||||
<label class="check-item${disabled ? ' is-disabled' : ''}">
|
|
||||||
<input type="${type}" name="${name}" value="${value}"${disabled ? ' disabled' : ''}>
|
|
||||||
<span>
|
|
||||||
<span class="check-item-label">${label}</span>
|
|
||||||
${desc ? `<span class="check-item-desc">${desc}</span>` : ''}
|
|
||||||
</span>
|
|
||||||
</label>`).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
function createCardHtml(id, prefillName, isSelf) {
|
|
||||||
const badge = isSelf ? '<span class="player-badge">Du</span>' : '';
|
|
||||||
const num = playerIds.indexOf(id) + 1;
|
|
||||||
const nameField = isSelf
|
|
||||||
? `<input type="text" id="p${id}-name" value="${prefillName}" readonly style="background:transparent;cursor:default;color:var(--color-muted);">`
|
|
||||||
: `<input type="text" id="p${id}-name" value="${prefillName}" placeholder="Name" autocomplete="off">`;
|
|
||||||
const inviteBtn = isSelf ? '' : `<button class="btn-invite" onclick="oeffneFreundeModal(${id})">👥 Einladen</button>`;
|
|
||||||
return `
|
|
||||||
<div class="player-card" id="player-${id}">
|
|
||||||
<div class="player-card-header">
|
|
||||||
<span class="player-title">Spieler ${num}</span>
|
|
||||||
${badge}
|
|
||||||
${inviteBtn}
|
|
||||||
<button class="player-remove" onclick="removePlayer(${id})">✕ Entfernen</button>
|
|
||||||
</div>
|
|
||||||
<div id="p${id}-body">
|
|
||||||
${buildPlayerBody(id, nameField, isSelf)}
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildPlayerBody(id, nameField, genderDisabled = false) {
|
|
||||||
return `
|
|
||||||
<div class="card-field">
|
|
||||||
<label>Name</label>
|
|
||||||
${nameField}
|
|
||||||
<div class="field-error" id="p${id}-name-err">Bitte Namen eingeben.</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-field">
|
|
||||||
<label>Geschlecht${genderDisabled ? ' <span style="font-size:0.75rem;color:var(--color-muted);">(unveränderlich)</span>' : ''}</label>
|
|
||||||
<div class="check-group">${buildCheckItems('p' + id + '-geschlecht', GESCHLECHTER, 'radio', genderDisabled)}</div>
|
|
||||||
<div class="field-error" id="p${id}-geschlecht-err">Bitte Geschlecht auswählen.</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-field">
|
|
||||||
<label>Spielt mit</label>
|
|
||||||
<div class="check-group">${buildCheckItems('p' + id + '-spieltmit', GESCHLECHTER, 'checkbox')}</div>
|
|
||||||
<div class="field-error" id="p${id}-spieltmit-err">Bitte mindestens eine Option wählen.</div>
|
|
||||||
<div class="field-error" id="p${id}-partner-err">Kein Mitspieler mit passendem Geschlecht vorhanden.</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-field">
|
|
||||||
<label>Rollen</label>
|
|
||||||
<div class="check-group">${buildCheckItems('p' + id + '-rollen', ROLLEN, 'checkbox')}</div>
|
|
||||||
<div class="field-error" id="p${id}-rollen-err">Bitte mindestens eine Rolle wählen.</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-field">
|
|
||||||
<label>Verfügbar</label>
|
|
||||||
<div class="check-group check-group--two-col">${buildCheckItems('p' + id + '-werkzeuge', WERKZEUGE, 'checkbox')}</div>
|
|
||||||
<div class="field-error" id="p${id}-werkzeuge-err">Bitte mindestens ein Werkzeug wählen.</div>
|
|
||||||
<div id="p${id}-chastity-hint" style="display:none;font-size:0.78rem;color:var(--color-muted);margin-top:0.4rem;font-style:italic;line-height:1.4;"></div>
|
|
||||||
</div>
|
|
||||||
<div class="card-field">
|
|
||||||
<label>Finale</label>
|
|
||||||
<label class="check-item is-checked" id="p${id}-sperre-label">
|
|
||||||
<input type="checkbox" id="p${id}-sperrenAufloesen" checked onchange="toggleSperreWarning(${id})">
|
|
||||||
<span class="check-item-label">Zeitstrafen vor dem Finale auflösen</span>
|
|
||||||
</label>
|
|
||||||
<div style="display:none; margin-top:0.4rem; font-size:0.78rem; color:var(--color-primary);" id="p${id}-sperre-warn">
|
|
||||||
⚠️ Hinweis: Zeitstrafen werden nicht aufgelöst. Diese Person könnte im Finale leer ausgehen.
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addPlayer(prefillName = '', isSelf = false) {
|
|
||||||
playerSeq++;
|
|
||||||
const id = playerSeq;
|
|
||||||
if (isSelf) selfPlayerId = id;
|
|
||||||
playerIds.push(id);
|
|
||||||
playerInvitations[id] = null;
|
|
||||||
document.getElementById('playersContainer')
|
|
||||||
.insertAdjacentHTML('beforeend', createCardHtml(id, prefillName, isSelf));
|
|
||||||
refreshRemoveButtons();
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removePlayer(id) {
|
|
||||||
const inv = playerInvitations[id];
|
|
||||||
// Einladung serverseitig canceln
|
|
||||||
if (inv && (inv.status === 'PENDING' || inv.status === 'ACCEPTED_OWN' || inv.status === 'ACCEPTED_HOST')) {
|
|
||||||
fetch(`/bdsm/einladung/${inv.einladungId}`, { method: 'DELETE' }).catch(() => {});
|
|
||||||
}
|
|
||||||
playerInvitations[id] = null;
|
|
||||||
|
|
||||||
if (playerIds.length <= 2) {
|
|
||||||
// Letzter Slot: nur leeren, nicht entfernen
|
|
||||||
const header = document.querySelector(`#player-${id} .player-card-header`);
|
|
||||||
if (header) {
|
|
||||||
header.querySelectorAll('.player-badge-pending,.player-badge-accepted').forEach(el => el.remove());
|
|
||||||
const invBtn = header.querySelector('.btn-invite');
|
|
||||||
if (invBtn) invBtn.style.display = '';
|
|
||||||
}
|
|
||||||
const body = document.getElementById(`p${id}-body`);
|
|
||||||
if (body) body.innerHTML = buildPlayerBody(id, `<input type="text" id="p${id}-name" placeholder="Name" autocomplete="off">`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('player-' + id)?.remove();
|
|
||||||
playerIds = playerIds.filter(x => x !== id);
|
|
||||||
delete playerInvitations[id];
|
|
||||||
refreshPlayerTitles();
|
|
||||||
refreshRemoveButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshPlayerTitles() {
|
|
||||||
playerIds.forEach((id, idx) => {
|
|
||||||
const el = document.querySelector(`#player-${id} .player-title`);
|
|
||||||
if (el) el.textContent = 'Spieler ' + (idx + 1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshRemoveButtons() {
|
|
||||||
playerIds.forEach((id, idx) => {
|
|
||||||
const btn = document.querySelector(`#player-${id} .player-remove`);
|
|
||||||
if (btn) btn.style.display = idx === 0 ? 'none' : '';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('change', e => {
|
|
||||||
const input = e.target;
|
|
||||||
if (input.type !== 'checkbox' && input.type !== 'radio') return;
|
|
||||||
if (input.type === 'radio') {
|
|
||||||
document.querySelectorAll(`input[name="${input.name}"]`).forEach(r => {
|
|
||||||
r.closest('.check-item')?.classList.toggle('is-checked', r.checked);
|
|
||||||
});
|
|
||||||
if (input.checked && input.name.endsWith('-geschlecht')) {
|
|
||||||
const prefix = input.name.slice(0, -'-geschlecht'.length);
|
|
||||||
const defaults = WERKZEUGE_DEFAULTS[input.value] || [];
|
|
||||||
document.querySelectorAll(`input[name="${prefix}-werkzeuge"]`).forEach(cb => {
|
|
||||||
if (cb.closest('.check-item')?.dataset.chastitylocked) return;
|
|
||||||
cb.checked = defaults.includes(cb.value);
|
|
||||||
cb.closest('.check-item')?.classList.toggle('is-checked', cb.checked);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const label = input.closest('.check-item');
|
|
||||||
if (label?.dataset.chastitylocked) {
|
|
||||||
// Revert immediately and flash hint
|
|
||||||
input.checked = false;
|
|
||||||
label.classList.remove('is-checked');
|
|
||||||
const card = label.closest('[id^="player-"]');
|
|
||||||
if (card) flashChastityHint(card.id.replace('player-', ''));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
label?.classList.toggle('is-checked', input.checked);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function getChecked(name) {
|
|
||||||
return [...document.querySelectorAll(`input[name="${name}"]:checked`)].map(el => el.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleSperreWarning(id) {
|
|
||||||
const cb = document.getElementById(`p${id}-sperrenAufloesen`);
|
|
||||||
const warn = document.getElementById(`p${id}-sperre-warn`);
|
|
||||||
const label = document.getElementById(`p${id}-sperre-label`);
|
|
||||||
if (!cb) return;
|
|
||||||
if (warn) warn.style.display = cb.checked ? 'none' : 'block';
|
|
||||||
if (label) label.classList.toggle('is-checked', cb.checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setFieldError(id, show) {
|
|
||||||
const el = document.getElementById(id);
|
|
||||||
if (el) el.style.display = show ? 'block' : 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Freunde-Modal ──
|
|
||||||
let currentInvitePlayerId = null;
|
|
||||||
let selectedFriend = null; // { userId, name }
|
|
||||||
|
|
||||||
async function oeffneFreundeModal(playerId) {
|
|
||||||
currentInvitePlayerId = playerId;
|
|
||||||
selectedFriend = null;
|
|
||||||
document.getElementById('friendSearch').value = '';
|
|
||||||
document.getElementById('friendDropdown').style.display = 'none';
|
|
||||||
document.getElementById('friendDropdown').innerHTML = '';
|
|
||||||
document.getElementById('selectedFriendBox').style.display = 'none';
|
|
||||||
document.getElementById('selectedFriendBox').textContent = '';
|
|
||||||
document.getElementById('btnEinladen').disabled = true;
|
|
||||||
document.getElementById('friendModal').style.display = 'flex';
|
|
||||||
if (freundeListe.length === 0) {
|
|
||||||
try {
|
|
||||||
const res = await fetch('/social/friends');
|
|
||||||
freundeListe = res.ok ? await res.json() : [];
|
|
||||||
} catch (_) { freundeListe = []; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterFreunde(query) {
|
|
||||||
selectedFriend = null;
|
|
||||||
document.getElementById('selectedFriendBox').style.display = 'none';
|
|
||||||
document.getElementById('btnEinladen').disabled = true;
|
|
||||||
const dropdown = document.getElementById('friendDropdown');
|
|
||||||
dropdown.innerHTML = '';
|
|
||||||
const q = query.trim().toLowerCase();
|
|
||||||
if (!q) { dropdown.style.display = 'none'; return; }
|
|
||||||
const invitedIds = new Set(
|
|
||||||
Object.values(playerInvitations)
|
|
||||||
.filter(inv => inv && (inv.status === 'PENDING' || inv.status === 'ACCEPTED_OWN' || inv.status === 'ACCEPTED_HOST'))
|
|
||||||
.map(inv => inv.inviteeId)
|
|
||||||
);
|
|
||||||
const matches = freundeListe.filter(f => (f.user.name || '').toLowerCase().includes(q) && !invitedIds.has(f.user.userId));
|
|
||||||
if (!matches.length) {
|
|
||||||
dropdown.innerHTML = '<div style="padding:0.6rem 0.75rem;color:var(--color-muted);font-size:0.9rem;">Keine Treffer.</div>';
|
|
||||||
dropdown.style.display = 'block';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
matches.forEach(f => {
|
|
||||||
const item = document.createElement('div');
|
|
||||||
item.className = 'friend-dropdown-item';
|
|
||||||
item.addEventListener('click', () => selectFriend(f.user.userId, f.user.name || 'Unbekannt'));
|
|
||||||
if (f.user.profilePicture) {
|
|
||||||
const img = document.createElement('img');
|
|
||||||
img.className = 'friend-avatar';
|
|
||||||
img.src = 'data:image/png;base64,' + f.user.profilePicture;
|
|
||||||
img.alt = '';
|
|
||||||
item.appendChild(img);
|
|
||||||
} else {
|
|
||||||
const av = document.createElement('div');
|
|
||||||
av.className = 'friend-avatar';
|
|
||||||
item.appendChild(av);
|
|
||||||
}
|
|
||||||
const span = document.createElement('span');
|
|
||||||
span.textContent = f.user.name || 'Unbekannt';
|
|
||||||
item.appendChild(span);
|
|
||||||
dropdown.appendChild(item);
|
|
||||||
});
|
|
||||||
dropdown.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectFriend(userId, name) {
|
|
||||||
selectedFriend = { userId, name };
|
|
||||||
document.getElementById('friendSearch').value = name;
|
|
||||||
document.getElementById('friendDropdown').style.display = 'none';
|
|
||||||
const box = document.getElementById('selectedFriendBox');
|
|
||||||
box.textContent = '✓ ' + name;
|
|
||||||
box.style.display = 'block';
|
|
||||||
document.getElementById('btnEinladen').disabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function confirmedEinladen() {
|
|
||||||
if (!selectedFriend) return;
|
|
||||||
await einladen(selectedFriend.userId, selectedFriend.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
function schliesseFriendModal() {
|
|
||||||
document.getElementById('friendModal').style.display = 'none';
|
|
||||||
currentInvitePlayerId = null;
|
|
||||||
selectedFriend = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function zeigePopup(title, text) {
|
|
||||||
document.getElementById('errorModalTitle').textContent = title;
|
|
||||||
document.getElementById('errorModalText').textContent = text;
|
|
||||||
document.getElementById('errorModal').style.display = 'flex';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function einladen(inviteeId, inviteeName) {
|
|
||||||
const id = currentInvitePlayerId;
|
|
||||||
schliesseFriendModal();
|
|
||||||
if (!id) return;
|
|
||||||
try {
|
|
||||||
const res = await fetch('/bdsm/einladung', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ setupId, slotIndex: id, inviteeId }),
|
|
||||||
});
|
|
||||||
if (res.status === 409) {
|
|
||||||
zeigePopup('Bereits eingeladen', `${inviteeName} ist bereits eingeladen oder nimmt schon am Spiel teil.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!res.ok) throw new Error();
|
|
||||||
const data = await res.json();
|
|
||||||
playerInvitations[id] = { einladungId: data.einladungId, status: 'PENDING', inviteeId, inviteeName };
|
|
||||||
renderPending(id);
|
|
||||||
startPoll();
|
|
||||||
} catch (_) {
|
|
||||||
zeigePopup('Fehler', 'Einladung konnte nicht gesendet werden. Bitte versuche es erneut.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderPending(id) {
|
|
||||||
const inv = playerInvitations[id];
|
|
||||||
if (!inv) return;
|
|
||||||
const body = document.getElementById(`p${id}-body`);
|
|
||||||
if (!body) return;
|
|
||||||
const headerInvBtn = document.querySelector(`#player-${id} .btn-invite`);
|
|
||||||
if (headerInvBtn) headerInvBtn.style.display = 'none';
|
|
||||||
|
|
||||||
if (inv.status === 'PENDING') {
|
|
||||||
// Badge nach "Spieler X" einfügen
|
|
||||||
const header = document.querySelector(`#player-${id} .player-card-header`);
|
|
||||||
header.querySelectorAll('.player-badge-pending,.player-badge-accepted').forEach(el => el.remove());
|
|
||||||
header.querySelector('.player-title').insertAdjacentHTML('afterend', `<span class="player-badge-pending">Ausstehend</span>`);
|
|
||||||
body.innerHTML = `
|
|
||||||
<div class="pending-info">
|
|
||||||
<div class="pending-name">${inv.inviteeName}</div>
|
|
||||||
<div>Einladung wurde gesendet – warte auf Antwort…</div>
|
|
||||||
<button class="btn-cancel-invite" style="margin-top:1rem;" onclick="cancelEinladung(${id})">Einladung abbrechen</button>
|
|
||||||
</div>`;
|
|
||||||
} else if (inv.status === 'ACCEPTED_OWN') {
|
|
||||||
const header = document.querySelector(`#player-${id} .player-card-header`);
|
|
||||||
header.querySelectorAll('.player-badge-pending,.player-badge-accepted').forEach(el => el.remove());
|
|
||||||
header.querySelector('.player-title').insertAdjacentHTML('afterend', `<span class="player-badge-accepted">✓ Eigenes Gerät</span>`);
|
|
||||||
body.innerHTML = `
|
|
||||||
<div class="pending-info">
|
|
||||||
<div class="pending-name">${inv.inviteeName}</div>
|
|
||||||
<div class="pending-mode">Spieler konfiguriert Präferenzen auf dem eigenen Gerät.</div>
|
|
||||||
<button class="btn-cancel-invite" style="margin-top:1rem;" onclick="cancelEinladung(${id})">Einladung abbrechen</button>
|
|
||||||
</div>`;
|
|
||||||
} else if (inv.status === 'ACCEPTED_HOST') {
|
|
||||||
const header = document.querySelector(`#player-${id} .player-card-header`);
|
|
||||||
header.querySelectorAll('.player-badge-pending,.player-badge-accepted').forEach(el => el.remove());
|
|
||||||
header.querySelector('.player-title').insertAdjacentHTML('afterend', `<span class="player-badge-accepted">✓ Host-Gerät</span>`);
|
|
||||||
const nameField = `<input type="text" id="p${id}-name" value="${inv.inviteeName}" readonly style="background:transparent;cursor:default;color:var(--color-muted);">`;
|
|
||||||
const hasGeschlecht = inv.defaults && inv.defaults.geschlecht;
|
|
||||||
body.innerHTML = buildPlayerBody(id, nameField, hasGeschlecht);
|
|
||||||
if (inv.defaults) {
|
|
||||||
restorePlayer(id, {
|
|
||||||
geschlecht: inv.defaults.geschlecht,
|
|
||||||
spieltMit: inv.defaults.spieltMit || [],
|
|
||||||
rollen: inv.defaults.rollen || [],
|
|
||||||
werkzeuge: inv.defaults.werkzeuge || [],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
pruefeChastityConstraint(id, inv.inviteeId);
|
|
||||||
} else if (inv.status === 'DECLINED' || inv.status === 'CANCELLED') {
|
|
||||||
// Slot wieder freigeben
|
|
||||||
const header = document.querySelector(`#player-${id} .player-card-header`);
|
|
||||||
header.querySelectorAll('.player-badge-pending,.player-badge-accepted').forEach(el => el.remove());
|
|
||||||
if (headerInvBtn) headerInvBtn.style.display = '';
|
|
||||||
playerInvitations[id] = null;
|
|
||||||
body.innerHTML = buildPlayerBody(id, `<input type="text" id="p${id}-name" placeholder="Name" autocomplete="off">`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function cancelEinladung(id) {
|
|
||||||
const inv = playerInvitations[id];
|
|
||||||
if (!inv) return;
|
|
||||||
await fetch(`/bdsm/einladung/${inv.einladungId}`, { method: 'DELETE' }).catch(() => {});
|
|
||||||
playerInvitations[id] = null;
|
|
||||||
// UI zurücksetzen
|
|
||||||
const header = document.querySelector(`#player-${id} .player-card-header`);
|
|
||||||
if (header) {
|
|
||||||
header.querySelectorAll('.player-badge-pending,.player-badge-accepted').forEach(el => el.remove());
|
|
||||||
const invBtn = header.querySelector('.btn-invite');
|
|
||||||
if (invBtn) invBtn.style.display = '';
|
|
||||||
}
|
|
||||||
const body = document.getElementById(`p${id}-body`);
|
|
||||||
if (body) body.innerHTML = buildPlayerBody(id, `<input type="text" id="p${id}-name" placeholder="Name" autocomplete="off">`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Polling ──
|
|
||||||
function startPoll() {
|
|
||||||
if (pollIntervalId) return;
|
|
||||||
pollIntervalId = setInterval(pollEinladungen, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopPoll() {
|
|
||||||
if (pollIntervalId) { clearInterval(pollIntervalId); pollIntervalId = null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pollEinladungen() {
|
|
||||||
const hasPending = Object.values(playerInvitations).some(inv => inv && inv.status === 'PENDING');
|
|
||||||
if (!hasPending) { stopPoll(); return; }
|
|
||||||
try {
|
|
||||||
const res = await fetch(`/bdsm/einladung?setupId=${setupId}`);
|
|
||||||
if (!res.ok) return;
|
|
||||||
const liste = await res.json();
|
|
||||||
for (const e of liste) {
|
|
||||||
const id = playerIds.find(pid => {
|
|
||||||
const inv = playerInvitations[pid];
|
|
||||||
return inv && inv.einladungId === e.einladungId;
|
|
||||||
});
|
|
||||||
if (!id) continue;
|
|
||||||
const inv = playerInvitations[id];
|
|
||||||
if (!inv || inv.status === e.status) continue;
|
|
||||||
inv.status = e.status;
|
|
||||||
if (e.status === 'DECLINED' || e.status === 'CANCELLED') {
|
|
||||||
showMessage(`${inv.inviteeName} hat die Einladung abgelehnt oder abgebrochen.`, 'error');
|
|
||||||
}
|
|
||||||
if (e.status === 'ACCEPTED_OWN' || e.status === 'ACCEPTED_HOST') {
|
|
||||||
// Profil + Defaults der eingeladenen Person laden
|
|
||||||
try {
|
|
||||||
const dRes = await fetch(`/user/${inv.inviteeId}/bdsm-defaults`);
|
|
||||||
if (dRes.ok) inv.defaults = await dRes.json();
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
renderPending(id);
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
updateWeiterBtn();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateWeiterBtn() {
|
|
||||||
const hasPending = Object.values(playerInvitations).some(inv => inv && inv.status === 'PENDING');
|
|
||||||
document.getElementById('weiterBtn').disabled = hasPending;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Validation & Weiter ──
|
|
||||||
function weiter() {
|
|
||||||
hideMessage();
|
|
||||||
|
|
||||||
const hasPending = Object.values(playerInvitations).some(inv => inv && inv.status === 'PENDING');
|
|
||||||
if (hasPending) {
|
|
||||||
showMessage('Bitte warte, bis alle Einladungen beantwortet wurden.', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let valid = true;
|
|
||||||
playerIds.forEach(id => setFieldError(`p${id}-partner-err`, false));
|
|
||||||
|
|
||||||
const mitspieler = playerIds.map(id => {
|
|
||||||
const inv = playerInvitations[id];
|
|
||||||
const isOwnDevice = inv && inv.status === 'ACCEPTED_OWN';
|
|
||||||
|
|
||||||
if (isOwnDevice) {
|
|
||||||
// Daten werden vom Spieler selbst auf dem eigenen Gerät konfiguriert
|
|
||||||
return {
|
|
||||||
name: inv.inviteeName,
|
|
||||||
geschlecht: null,
|
|
||||||
spieltMit: [], rollen: [], werkzeuge: [],
|
|
||||||
userId: inv.inviteeId,
|
|
||||||
eigenesGeraet: true,
|
|
||||||
einladungId: inv.einladungId,
|
|
||||||
sperrenVorFinaleAufloesen: true, // Wird auf dem eigenen Gerät konfiguriert
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = document.getElementById(`p${id}-name`)?.value.trim() || '';
|
|
||||||
const geschlecht = getChecked(`p${id}-geschlecht`);
|
|
||||||
const spieltMit = getChecked(`p${id}-spieltmit`);
|
|
||||||
const rollen = getChecked(`p${id}-rollen`);
|
|
||||||
const werkzeuge = getChecked(`p${id}-werkzeuge`);
|
|
||||||
|
|
||||||
setFieldError(`p${id}-name-err`, !name);
|
|
||||||
setFieldError(`p${id}-geschlecht-err`, geschlecht.length === 0);
|
|
||||||
setFieldError(`p${id}-spieltmit-err`, spieltMit.length === 0);
|
|
||||||
setFieldError(`p${id}-rollen-err`, rollen.length === 0);
|
|
||||||
setFieldError(`p${id}-werkzeuge-err`, werkzeuge.length === 0);
|
|
||||||
|
|
||||||
if (!name || geschlecht.length === 0 || spieltMit.length === 0 || rollen.length === 0 || werkzeuge.length === 0) {
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sperrenAufloesen = document.getElementById(`p${id}-sperrenAufloesen`);
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
geschlecht: geschlecht[0] || null,
|
|
||||||
spieltMit, rollen, werkzeuge,
|
|
||||||
userId: inv ? inv.inviteeId : (id === selfPlayerId ? myUserId : null),
|
|
||||||
eigenesGeraet: false,
|
|
||||||
sperrenVorFinaleAufloesen: sperrenAufloesen ? sperrenAufloesen.checked : true,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!valid) { showMessage('Bitte alle Felder für jeden Spieler ausfüllen.', 'error'); return; }
|
|
||||||
|
|
||||||
const configuredMitspieler = mitspieler.filter(p => !p.eigenesGeraet);
|
|
||||||
const hasOwnDeviceInRoleCheck = mitspieler.some(p => p.eigenesGeraet);
|
|
||||||
if (!hasOwnDeviceInRoleCheck) {
|
|
||||||
const allRoles = new Set(configuredMitspieler.flatMap(p => p.rollen));
|
|
||||||
const missingRoles = Object.keys(ROLE_LABELS).filter(r => !allRoles.has(r));
|
|
||||||
if (missingRoles.length > 0) {
|
|
||||||
showMessage('Folgende Rollen müssen mindestens einmal vergeben sein: ' +
|
|
||||||
missingRoles.map(r => ROLE_LABELS[r]).join(', '), 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasOwnDevicePlayers = mitspieler.some(p => p.eigenesGeraet);
|
|
||||||
if (!hasOwnDevicePlayers) {
|
|
||||||
let partnerFehler = false;
|
|
||||||
configuredMitspieler.forEach((player, i) => {
|
|
||||||
const andereGeschlechter = configuredMitspieler.filter((_, j) => j !== i).map(p => p.geschlecht);
|
|
||||||
const hatPartner = player.spieltMit.some(g => andereGeschlechter.includes(g));
|
|
||||||
if (!hatPartner) {
|
|
||||||
const globalIdx = mitspieler.indexOf(player);
|
|
||||||
setFieldError(`p${playerIds[globalIdx]}-partner-err`, true);
|
|
||||||
partnerFehler = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (partnerFehler) { showMessage('Mindestens ein Spieler hat keinen kompatiblen Mitspieler.', 'error'); return; }
|
|
||||||
}
|
|
||||||
|
|
||||||
const settings = JSON.parse(sessionStorage.getItem('bdsm-session-settings'));
|
|
||||||
const sessionSetup = JSON.stringify({ settings, mitspieler });
|
|
||||||
sessionStorage.setItem('bdsm-session-setup', sessionSetup);
|
|
||||||
// Draft in DB aktualisieren
|
|
||||||
fetch('/bdsm/setup-draft', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
setupId,
|
|
||||||
setupJson: sessionSetup,
|
|
||||||
}),
|
|
||||||
}).catch(() => {});
|
|
||||||
window.location.href = '/bdsmtasks.html';
|
|
||||||
}
|
|
||||||
|
|
||||||
function showMessage(text, type) {
|
|
||||||
const el = document.getElementById('message');
|
|
||||||
el.textContent = text; el.className = `message ${type}`; el.style.display = 'block';
|
|
||||||
}
|
|
||||||
function hideMessage() { document.getElementById('message').style.display = 'none'; }
|
|
||||||
|
|
||||||
// ── Chastity-Constraint: gesperrtes Werkzeug angehakt + disabled ──
|
|
||||||
async function pruefeChastityConstraint(playerId, userId) {
|
|
||||||
if (!userId) return;
|
|
||||||
try {
|
|
||||||
const res = await fetch(`/bdsm/chastity-constraint?userId=${userId}`);
|
|
||||||
if (!res.ok) return;
|
|
||||||
const { lockedWerkzeug } = await res.json();
|
|
||||||
if (!lockedWerkzeug) return;
|
|
||||||
const locked = lockedWerkzeug === 'BOTH' ? ['VAGINA', 'PENIS'] : [lockedWerkzeug];
|
|
||||||
locked.forEach(w => sperrWerkzeugCheckbox(playerId, w));
|
|
||||||
// Hinweis-Text im hidden div speichern, Popup beim Klick auf Checkbox
|
|
||||||
const hintEl = document.getElementById(`p${playerId}-chastity-hint`);
|
|
||||||
if (hintEl) {
|
|
||||||
hintEl.dataset.hintText = '🔒 Das System weiß aus halbwegs verlässlicher Quelle, dass diese Person dieses Körperteil gerade nicht einsetzen kann – und lässt sich hier auch nicht austricksen.';
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sperrWerkzeugCheckbox(playerId, werkzeug) {
|
|
||||||
const cb = document.querySelector(`input[name="p${playerId}-werkzeuge"][value="${werkzeug}"]`);
|
|
||||||
if (!cb) return;
|
|
||||||
cb.checked = false;
|
|
||||||
cb.disabled = false;
|
|
||||||
const label = cb.closest('.check-item');
|
|
||||||
if (label) {
|
|
||||||
label.classList.remove('is-checked', 'is-disabled');
|
|
||||||
label.dataset.chastitylocked = '1';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function flashChastityHint(playerId) {
|
|
||||||
const hintEl = document.getElementById(`p${playerId}-chastity-hint`);
|
|
||||||
const text = hintEl?.dataset.hintText;
|
|
||||||
if (!text) return;
|
|
||||||
document.getElementById('errorModalTitle').textContent = 'Nicht verfügbar';
|
|
||||||
document.getElementById('errorModalText').textContent = text;
|
|
||||||
document.getElementById('errorModal').style.display = 'flex';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Klick auf gesperrte Checkbox: Hint aufblinken lassen
|
|
||||||
document.addEventListener('click', e => {
|
|
||||||
const label = e.target.closest('.check-item[data-chastitylocked]');
|
|
||||||
if (!label) return;
|
|
||||||
const card = label.closest('[id^="player-"]');
|
|
||||||
if (!card) return;
|
|
||||||
const playerId = card.id.replace('player-', '');
|
|
||||||
flashChastityHint(playerId);
|
|
||||||
});
|
|
||||||
|
|
||||||
function restorePlayer(id, data) {
|
|
||||||
if (data.geschlecht) {
|
|
||||||
const radio = document.querySelector(`input[name="p${id}-geschlecht"][value="${data.geschlecht}"]`);
|
|
||||||
if (radio) { radio.checked = true; radio.closest('.check-item')?.classList.add('is-checked'); }
|
|
||||||
}
|
|
||||||
(data.spieltMit || []).forEach(val => {
|
|
||||||
const cb = document.querySelector(`input[name="p${id}-spieltmit"][value="${val}"]`);
|
|
||||||
if (cb) { cb.checked = true; cb.closest('.check-item')?.classList.add('is-checked'); }
|
|
||||||
});
|
|
||||||
(data.rollen || []).forEach(val => {
|
|
||||||
const cb = document.querySelector(`input[name="p${id}-rollen"][value="${val}"]`);
|
|
||||||
if (cb) { cb.checked = true; cb.closest('.check-item')?.classList.add('is-checked'); }
|
|
||||||
});
|
|
||||||
(data.werkzeuge || []).forEach(val => {
|
|
||||||
const cb = document.querySelector(`input[name="p${id}-werkzeuge"][value="${val}"]`);
|
|
||||||
if (cb) { cb.checked = true; cb.closest('.check-item')?.classList.add('is-checked'); }
|
|
||||||
});
|
|
||||||
if (data.sperrenVorFinaleAufloesen === false) {
|
|
||||||
const cb = document.getElementById(`p${id}-sperrenAufloesen`);
|
|
||||||
if (cb) { cb.checked = false; toggleSperreWarning(id); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Einladungen aus DB wiederherstellen ──
|
|
||||||
async function ladeEinladungenAusDb(userIdToInfo) {
|
|
||||||
// userIdToInfo: { [userId]: { playerId, name } } oder null (dann Matching per slotIndex)
|
|
||||||
try {
|
|
||||||
const res = await fetch(`/bdsm/einladung?setupId=${setupId}`);
|
|
||||||
if (!res.ok) return;
|
|
||||||
const einladungen = await res.json();
|
|
||||||
const aktive = einladungen.filter(e =>
|
|
||||||
e.status === 'PENDING' || e.status === 'ACCEPTED_OWN' || e.status === 'ACCEPTED_HOST');
|
|
||||||
for (const e of aktive) {
|
|
||||||
let playerId;
|
|
||||||
if (userIdToInfo && userIdToInfo[e.inviteeId]) {
|
|
||||||
playerId = userIdToInfo[e.inviteeId].playerId;
|
|
||||||
} else {
|
|
||||||
// Fallback: slotIndex direkt als playerId nutzen (wenn playerIds das enthält)
|
|
||||||
playerId = playerIds.find(pid => pid === e.slotIndex);
|
|
||||||
}
|
|
||||||
if (!playerId) continue;
|
|
||||||
const inviteeName = (userIdToInfo?.[e.inviteeId]?.name) || e.inviteeName || '';
|
|
||||||
playerInvitations[playerId] = {
|
|
||||||
einladungId: e.einladungId,
|
|
||||||
status: e.status,
|
|
||||||
inviteeId: e.inviteeId,
|
|
||||||
inviteeName,
|
|
||||||
};
|
|
||||||
if (e.status === 'ACCEPTED_OWN' || e.status === 'ACCEPTED_HOST') {
|
|
||||||
try {
|
|
||||||
const dRes = await fetch(`/user/${e.inviteeId}/bdsm-defaults`);
|
|
||||||
if (dRes.ok) playerInvitations[playerId].defaults = await dRes.json();
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
renderPending(playerId);
|
|
||||||
}
|
|
||||||
if (aktive.some(e => e.status === 'PENDING')) startPoll();
|
|
||||||
updateWeiterBtn();
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Init ──
|
|
||||||
async function init() {
|
|
||||||
const ok = await ladeSessionOderDraft();
|
|
||||||
if (!ok) return;
|
|
||||||
|
|
||||||
// myUserId immer laden (wird für userId des Host-Spielers benötigt)
|
|
||||||
const user = await fetch('/login/me').then(r => r.ok ? r.json() : null).catch(() => null);
|
|
||||||
myUserId = user?.userId || null;
|
|
||||||
|
|
||||||
const savedSetup = sessionStorage.getItem('bdsm-session-setup');
|
|
||||||
if (savedSetup) {
|
|
||||||
const { mitspieler } = JSON.parse(savedSetup);
|
|
||||||
const userIdToInfo = {};
|
|
||||||
mitspieler.forEach((p, i) => {
|
|
||||||
const id = addPlayer(p.name, i === 0);
|
|
||||||
restorePlayer(id, p);
|
|
||||||
if (p.userId) userIdToInfo[p.userId] = { playerId: id, name: p.name };
|
|
||||||
});
|
|
||||||
mitspieler.forEach((p, i) => { if (p.userId) pruefeChastityConstraint(playerIds[i], p.userId); });
|
|
||||||
await ladeEinladungenAusDb(userIdToInfo);
|
|
||||||
} else {
|
|
||||||
const defaults = await fetch('/user/me/bdsm-defaults').then(r => r.ok ? r.json() : {}).catch(() => ({}));
|
|
||||||
addPlayer(user ? user.name : '', true);
|
|
||||||
addPlayer();
|
|
||||||
const selfId = playerIds[0];
|
|
||||||
restorePlayer(selfId, {
|
|
||||||
geschlecht: user?.geschlecht || null,
|
|
||||||
spieltMit: defaults.spieltMit || [],
|
|
||||||
rollen: defaults.rollen || [],
|
|
||||||
werkzeuge: defaults.werkzeuge || [],
|
|
||||||
});
|
|
||||||
if (myUserId) pruefeChastityConstraint(selfId, myUserId);
|
|
||||||
// Auch für frischen Start: evtl. noch offene Einladungen aus vorheriger Session
|
|
||||||
await ladeEinladungenAusDb(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
init();
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -2,351 +2,10 @@
|
|||||||
<html lang="de">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
<meta http-equiv="refresh" content="0;url=/neubdsm.html">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<title>BDSM Game</title>
|
||||||
<title>BDSM Game – Aufgaben-Gruppen – XXX The Game</title>
|
|
||||||
<link rel="stylesheet" href="/css/variables.css">
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
<style>
|
|
||||||
.session-setup { }
|
|
||||||
|
|
||||||
.setup-section { margin-bottom: 2.5rem; }
|
|
||||||
.setup-section h2 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
border-bottom: 1px solid var(--color-secondary);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
.select-all-label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
.select-all-label input {
|
|
||||||
accent-color: var(--color-primary);
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gruppe-list { list-style: none; padding: 0; margin: 0; }
|
|
||||||
.gruppe-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.6rem;
|
|
||||||
padding: 0.6rem 0.85rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--color-card);
|
|
||||||
border: 1px solid var(--color-secondary);
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color 0.15s;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
.gruppe-item.is-checked { border-color: var(--color-primary); }
|
|
||||||
.gruppe-item input {
|
|
||||||
accent-color: var(--color-primary);
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.gruppe-item span { flex: 1; min-width: 0; }
|
|
||||||
.item-img {
|
|
||||||
width: 38px;
|
|
||||||
height: 38px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 6px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.gruppe-item-name {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
.gruppe-item-desc {
|
|
||||||
display: block;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: var(--color-muted);
|
|
||||||
margin-top: 0.15rem;
|
|
||||||
}
|
|
||||||
.empty-hint {
|
|
||||||
color: var(--color-muted);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-style: italic;
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="app">
|
<body>
|
||||||
<div class="main">
|
<script>window.location.replace('/neubdsm.html');</script>
|
||||||
<div class="content session-setup">
|
|
||||||
|
|
||||||
<h1>BDSM Game</h1>
|
|
||||||
<p style="margin-bottom:2rem;">Schritt 3 von 4 – Aufgaben</p>
|
|
||||||
|
|
||||||
<div class="setup-section" id="sectionOwn">
|
|
||||||
<h2><label class="select-all-label">
|
|
||||||
<input type="checkbox" class="select-all-cb" data-list="listOwn">
|
|
||||||
Eigene Gruppen
|
|
||||||
</label></h2>
|
|
||||||
<ul class="gruppe-list" id="listOwn"></ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="setup-section" id="sectionSubscribed">
|
|
||||||
<h2><label class="select-all-label">
|
|
||||||
<input type="checkbox" class="select-all-cb" data-list="listSubscribed">
|
|
||||||
Abonnierte Gruppen
|
|
||||||
</label></h2>
|
|
||||||
<ul class="gruppe-list" id="listSubscribed"></ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="setup-section" id="sectionSystem">
|
|
||||||
<h2><label class="select-all-label">
|
|
||||||
<input type="checkbox" class="select-all-cb" data-list="listSystem">
|
|
||||||
System-Gruppen
|
|
||||||
</label></h2>
|
|
||||||
<ul class="gruppe-list" id="listSystem"></ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="position:relative; margin-top:2rem;">
|
|
||||||
<div class="message" id="message" style="position:absolute; bottom:calc(100% + 0.5rem); left:0; right:0; margin:0;"></div>
|
|
||||||
<div style="display:flex; gap:1rem;">
|
|
||||||
<button style="flex:1;" class="secondary" onclick="window.location.href='/bdsmplayers.html'">← Zurück</button>
|
|
||||||
<button style="flex:2;" onclick="weiter()">Weiter</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/js/sidebar.js"></script>
|
|
||||||
<script>
|
|
||||||
let savedGruppen = new Set();
|
|
||||||
|
|
||||||
async function ladeSessionOderDraft() {
|
|
||||||
if (sessionStorage.getItem('bdsm-session-setup')) return true;
|
|
||||||
try {
|
|
||||||
const res = await fetch('/bdsm/setup-draft');
|
|
||||||
if (!res.ok) { window.location.replace('/bdsm.html'); return false; }
|
|
||||||
const draft = await res.json();
|
|
||||||
if (draft.setupId) sessionStorage.setItem('bdsm-setup-id', draft.setupId);
|
|
||||||
if (draft.settingsJson) sessionStorage.setItem('bdsm-session-settings', draft.settingsJson);
|
|
||||||
if (draft.setupJson) sessionStorage.setItem('bdsm-session-setup', draft.setupJson);
|
|
||||||
if (draft.gruppenJson) sessionStorage.setItem('bdsm-session-gruppen', draft.gruppenJson);
|
|
||||||
if (!draft.setupJson) { window.location.replace('/bdsm.html'); return false; }
|
|
||||||
return true;
|
|
||||||
} catch (_) {
|
|
||||||
window.location.replace('/bdsm.html');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let warnungsAkzeptiert = false;
|
|
||||||
|
|
||||||
document.addEventListener('change', e => {
|
|
||||||
const cb = e.target;
|
|
||||||
if (cb.type !== 'checkbox') return;
|
|
||||||
|
|
||||||
if (cb.classList.contains('select-all-cb')) {
|
|
||||||
// Alle Gruppen in dieser Sektion (de-)selektieren
|
|
||||||
const list = document.getElementById(cb.dataset.list);
|
|
||||||
list.querySelectorAll('input[type="checkbox"]').forEach(itemCb => {
|
|
||||||
itemCb.checked = cb.checked;
|
|
||||||
itemCb.closest('.gruppe-item')?.classList.toggle('is-checked', cb.checked);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Einzelne Gruppe: is-checked-Klasse anpassen und Alles-Haken aktualisieren
|
|
||||||
cb.closest('.gruppe-item')?.classList.toggle('is-checked', cb.checked);
|
|
||||||
updateSelectAll(cb.closest('.gruppe-list'));
|
|
||||||
}
|
|
||||||
|
|
||||||
warnungsAkzeptiert = false;
|
|
||||||
hideMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateSelectAll(list) {
|
|
||||||
if (!list) return;
|
|
||||||
const itemCbs = [...list.querySelectorAll('input[type="checkbox"]')];
|
|
||||||
if (!itemCbs.length) return;
|
|
||||||
const section = list.closest('.setup-section');
|
|
||||||
const selectAllCb = section?.querySelector('.select-all-cb');
|
|
||||||
if (!selectAllCb) return;
|
|
||||||
const checkedCount = itemCbs.filter(cb => cb.checked).length;
|
|
||||||
selectAllCb.checked = checkedCount === itemCbs.length;
|
|
||||||
selectAllCb.indeterminate = checkedCount > 0 && checkedCount < itemCbs.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderList(containerId, gruppen) {
|
|
||||||
const ul = document.getElementById(containerId);
|
|
||||||
const section = ul.closest('.setup-section');
|
|
||||||
const selectAllWrap = section?.querySelector('.select-all-label');
|
|
||||||
|
|
||||||
if (!gruppen.length) {
|
|
||||||
ul.innerHTML = '<li class="empty-hint">Keine Gruppen vorhanden.</li>';
|
|
||||||
if (selectAllWrap) selectAllWrap.style.visibility = 'hidden';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.innerHTML = gruppen.map(g => {
|
|
||||||
const checked = savedGruppen.has(g.gruppenId);
|
|
||||||
return `
|
|
||||||
<li>
|
|
||||||
<label class="gruppe-item${checked ? ' is-checked' : ''}">
|
|
||||||
<input type="checkbox" value="${g.gruppenId}"${checked ? ' checked' : ''}>
|
|
||||||
<span>
|
|
||||||
<span class="gruppe-item-name">${g.name}</span>
|
|
||||||
${g.beschreibung ? `<span class="gruppe-item-desc">${g.beschreibung}</span>` : ''}
|
|
||||||
</span>
|
|
||||||
${g.bild ? `<img class="item-img" src="data:image/png;base64,${g.bild}" alt="">` : ''}
|
|
||||||
</label>
|
|
||||||
</li>`;
|
|
||||||
}).join('');
|
|
||||||
|
|
||||||
updateSelectAll(ul);
|
|
||||||
}
|
|
||||||
|
|
||||||
const GESCHLECHT_LABEL = { WEIBLICH: 'Weiblich', DIVERS: 'Divers', MAENNLICH: 'Männlich' };
|
|
||||||
|
|
||||||
function validateContent(content, settings, mitspieler) {
|
|
||||||
const errors = [], warnings = [];
|
|
||||||
|
|
||||||
const aufgabenByLevel = {};
|
|
||||||
content.aufgaben.forEach(a => {
|
|
||||||
const l = a.level ?? 0;
|
|
||||||
aufgabenByLevel[l] = (aufgabenByLevel[l] || 0) + 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const [level, count] of Object.entries(aufgabenByLevel)) {
|
|
||||||
if (count < 5) errors.push(`Level ${level}: Nur ${count} Aufgabe(n) – Minimum 5 erforderlich`);
|
|
||||||
else if (count < 10) warnings.push(`Level ${level}: Nur ${count} Aufgaben – empfohlen ≥ 10`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.wahrscheinlichkeitStrafe > 1) {
|
|
||||||
const strafenByLevel = {};
|
|
||||||
content.strafen.forEach(s => {
|
|
||||||
const l = s.level ?? 0;
|
|
||||||
strafenByLevel[l] = (strafenByLevel[l] || 0) + 1;
|
|
||||||
});
|
|
||||||
for (const level of Object.keys(aufgabenByLevel)) {
|
|
||||||
const count = strafenByLevel[level] || 0;
|
|
||||||
if (count < 1) errors.push(`Level ${level}: Keine Strafe vorhanden`);
|
|
||||||
else if (count < 2) warnings.push(`Level ${level}: Nur ${count} Strafe(n) – empfohlen ≥ 2`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.wahrscheinlichkeitSperre > 1) {
|
|
||||||
const count = content.sperren.length;
|
|
||||||
if (count < 1) errors.push('Keine Zeitstrafen vorhanden');
|
|
||||||
else if (count < 5) warnings.push(`Nur ${count} Zeitstrafe(n) – empfohlen ≥ 5`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const beteiligtGeschlecht = [...new Set((mitspieler || []).map(p => p.geschlecht).filter(Boolean))];
|
|
||||||
for (const g of beteiligtGeschlecht) {
|
|
||||||
const count = (content.finisher || []).filter(f => f.geschlecht === g).length;
|
|
||||||
if (count < 1) errors.push(`Kein Finisher für ${GESCHLECHT_LABEL[g] || g} vorhanden`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { errors, warnings };
|
|
||||||
}
|
|
||||||
|
|
||||||
function showValidation(errors, warnings, mitHinweis) {
|
|
||||||
const el = document.getElementById('message');
|
|
||||||
el.innerHTML = [
|
|
||||||
...errors.map(e => `<div>✕ ${e}</div>`),
|
|
||||||
...warnings.map(w => `<div>⚠ ${w}</div>`),
|
|
||||||
...(mitHinweis ? ['<div style="margin-top:0.5rem;font-style:italic;">Nochmals auf Weiter klicken um fortzufahren.</div>'] : []),
|
|
||||||
].join('');
|
|
||||||
el.className = `message ${errors.length ? 'error' : 'warning'}`;
|
|
||||||
el.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function showMessage(text, type) {
|
|
||||||
const el = document.getElementById('message');
|
|
||||||
const icon = type === 'error' ? '✕ ' : type === 'warning' ? '⚠ ' : '';
|
|
||||||
el.textContent = icon + text;
|
|
||||||
el.className = `message ${type}`;
|
|
||||||
el.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideMessage() {
|
|
||||||
document.getElementById('message').style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function weiter() {
|
|
||||||
hideMessage();
|
|
||||||
const selected = [...document.querySelectorAll('.gruppe-list input[type="checkbox"]:checked')]
|
|
||||||
.map(cb => cb.value);
|
|
||||||
if (selected.length === 0) {
|
|
||||||
showMessage('Bitte mindestens eine Aufgaben-Gruppe auswählen.', 'error');
|
|
||||||
warnungsAkzeptiert = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const btn = document.querySelector('button[onclick="weiter()"]');
|
|
||||||
btn.disabled = true;
|
|
||||||
const gruppen = await Promise.all(
|
|
||||||
selected.map(id => fetch(`/gruppe/${id}`).then(r => r.ok ? r.json() : null))
|
|
||||||
);
|
|
||||||
btn.disabled = false;
|
|
||||||
|
|
||||||
const content = { aufgaben: [], strafen: [], sperren: [], finisher: [] };
|
|
||||||
gruppen.filter(Boolean).forEach(g => {
|
|
||||||
content.aufgaben.push(...(g.aufgaben || []));
|
|
||||||
content.strafen.push(...(g.strafen || []));
|
|
||||||
content.sperren.push(...(g.sperren || []));
|
|
||||||
content.finisher.push(...(g.finisher || []));
|
|
||||||
});
|
|
||||||
|
|
||||||
const settings = JSON.parse(sessionStorage.getItem('bdsm-session-settings'));
|
|
||||||
const setup = JSON.parse(sessionStorage.getItem('bdsm-session-setup'));
|
|
||||||
const { errors, warnings } = validateContent(content, settings, setup?.mitspieler || []);
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
|
||||||
showValidation(errors, warnings, false);
|
|
||||||
warnungsAkzeptiert = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (warnings.length > 0 && !warnungsAkzeptiert) {
|
|
||||||
showValidation([], warnings, true);
|
|
||||||
warnungsAkzeptiert = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionStorage.setItem('bdsm-session-gruppen', JSON.stringify(selected));
|
|
||||||
// Draft in DB aktualisieren
|
|
||||||
fetch('/bdsm/setup-draft', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ gruppenJson: JSON.stringify(selected) }),
|
|
||||||
}).catch(() => {});
|
|
||||||
window.location.href = '/bdsmtoys.html';
|
|
||||||
}
|
|
||||||
|
|
||||||
(async function init() {
|
|
||||||
const ok = await ladeSessionOderDraft();
|
|
||||||
if (!ok) return;
|
|
||||||
savedGruppen = new Set(JSON.parse(sessionStorage.getItem('bdsm-session-gruppen') || '[]'));
|
|
||||||
|
|
||||||
try {
|
|
||||||
const [own, abo, system] = await Promise.all([
|
|
||||||
fetch('/gruppe/list/user?page=0&size=500').then(r => r.ok ? r.json() : { content: [] }),
|
|
||||||
fetch('/abo/list?page=0&size=500').then(r => r.ok ? r.json() : { content: [] }),
|
|
||||||
fetch('/gruppe/list/system?page=0&size=500').then(r => r.ok ? r.json() : { content: [] }),
|
|
||||||
]);
|
|
||||||
renderList('listOwn', own.content || []);
|
|
||||||
renderList('listSubscribed', abo.content || []);
|
|
||||||
renderList('listSystem', system.content || []);
|
|
||||||
} catch (err) { console.error('[bdsmtasks] Fehler beim Laden:', err); }
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -2,371 +2,10 @@
|
|||||||
<html lang="de">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
<meta http-equiv="refresh" content="0;url=/neubdsm.html">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<title>BDSM Game</title>
|
||||||
<title>BDSM Game – Toys – XXX The Game</title>
|
|
||||||
<link rel="stylesheet" href="/css/variables.css">
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
<style>
|
|
||||||
.session-setup { }
|
|
||||||
|
|
||||||
.setup-section { margin-bottom: 2.5rem; }
|
|
||||||
.setup-section h2 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
border-bottom: 1px solid var(--color-secondary);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toy-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.6rem;
|
|
||||||
padding: 0.6rem 0.85rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--color-card);
|
|
||||||
border: 1px solid var(--color-secondary);
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color 0.15s;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
.toy-item.is-checked { border-color: var(--color-primary); }
|
|
||||||
.toy-item input {
|
|
||||||
accent-color: var(--color-primary);
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.toy-item span { flex: 1; min-width: 0; }
|
|
||||||
.item-img {
|
|
||||||
width: 38px;
|
|
||||||
height: 38px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 6px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.toy-item-name { font-size: 0.95rem; font-weight: 600; color: var(--color-text); }
|
|
||||||
.toy-item-desc { display: block; font-size: 0.8rem; color: var(--color-muted); margin-top: 0.15rem; }
|
|
||||||
.no-toys { color: var(--color-muted); font-size: 0.875rem; font-style: italic; }
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="app">
|
<body>
|
||||||
<div class="main">
|
<script>window.location.replace('/neubdsm.html');</script>
|
||||||
<div class="content session-setup">
|
|
||||||
|
|
||||||
<h1>BDSM Game</h1>
|
|
||||||
<p style="margin-bottom:2rem;">Schritt 4 von 4 – Toys</p>
|
|
||||||
|
|
||||||
<div class="setup-section">
|
|
||||||
<h2>Benötigte Toys</h2>
|
|
||||||
<p style="font-size:0.85rem; color:var(--color-muted); margin-bottom:1.25rem;">
|
|
||||||
Deaktiviere Toys, die nicht zur Verfügung stehen. Aufgaben, die diese benötigen, werden nicht gespielt.
|
|
||||||
</p>
|
|
||||||
<div id="toyList"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="position:relative; margin-top:2rem;">
|
|
||||||
<div class="message" id="message" style="position:absolute; bottom:calc(100% + 0.5rem); left:0; right:0; margin:0;"></div>
|
|
||||||
<div style="display:flex; gap:1rem;">
|
|
||||||
<button style="flex:1;" class="secondary" onclick="window.location.href='/bdsmtasks.html'">← Zurück</button>
|
|
||||||
<button style="flex:2;" onclick="spielStarten()">Spiel starten</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/js/sidebar.js"></script>
|
|
||||||
<script>
|
|
||||||
let savedGruppen = JSON.parse(sessionStorage.getItem('bdsm-session-gruppen') || 'null');
|
|
||||||
|
|
||||||
async function ladeSessionOderDraft() {
|
|
||||||
if (savedGruppen) return true;
|
|
||||||
try {
|
|
||||||
const res = await fetch('/bdsm/setup-draft');
|
|
||||||
if (!res.ok) { window.location.replace('/bdsm.html'); return false; }
|
|
||||||
const draft = await res.json();
|
|
||||||
if (draft.setupId) sessionStorage.setItem('bdsm-setup-id', draft.setupId);
|
|
||||||
if (draft.settingsJson) sessionStorage.setItem('bdsm-session-settings', draft.settingsJson);
|
|
||||||
if (draft.setupJson) sessionStorage.setItem('bdsm-session-setup', draft.setupJson);
|
|
||||||
if (draft.gruppenJson) { sessionStorage.setItem('bdsm-session-gruppen', draft.gruppenJson); savedGruppen = JSON.parse(draft.gruppenJson); }
|
|
||||||
if (!savedGruppen) { window.location.replace('/bdsm.html'); return false; }
|
|
||||||
return true;
|
|
||||||
} catch (_) {
|
|
||||||
window.location.replace('/bdsm.html');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Previously saved toy selection (when navigating back from game page)
|
|
||||||
const savedToysRaw = sessionStorage.getItem('bdsm-session-toys');
|
|
||||||
const savedToyIds = savedToysRaw
|
|
||||||
? new Set(JSON.parse(savedToysRaw).map(t => t.toyId))
|
|
||||||
: null; // null = first visit → default all checked
|
|
||||||
|
|
||||||
// All content collected from selected groups
|
|
||||||
const allContent = { aufgaben: [], strafen: [], sperren: [], finisher: [] };
|
|
||||||
|
|
||||||
let warnungsAkzeptiert = false;
|
|
||||||
|
|
||||||
document.addEventListener('change', e => {
|
|
||||||
const cb = e.target;
|
|
||||||
if (cb.type !== 'checkbox') return;
|
|
||||||
cb.closest('.toy-item')?.classList.toggle('is-checked', cb.checked);
|
|
||||||
warnungsAkzeptiert = false;
|
|
||||||
hideMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
const GESCHLECHT_LABEL = { WEIBLICH: 'Weiblich', DIVERS: 'Divers', MAENNLICH: 'Männlich' };
|
|
||||||
|
|
||||||
function validateContent(content, settings, mitspieler) {
|
|
||||||
const errors = [], warnings = [];
|
|
||||||
|
|
||||||
const aufgabenByLevel = {};
|
|
||||||
content.aufgaben.forEach(a => {
|
|
||||||
const l = a.level ?? 0;
|
|
||||||
aufgabenByLevel[l] = (aufgabenByLevel[l] || 0) + 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const [level, count] of Object.entries(aufgabenByLevel)) {
|
|
||||||
if (count < 5) errors.push(`Level ${level}: Nur ${count} Aufgabe(n) – Minimum 5 erforderlich`);
|
|
||||||
else if (count < 10) warnings.push(`Level ${level}: Nur ${count} Aufgaben – empfohlen ≥ 10`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.wahrscheinlichkeitStrafe > 1) {
|
|
||||||
const strafenByLevel = {};
|
|
||||||
content.strafen.forEach(s => {
|
|
||||||
const l = s.level ?? 0;
|
|
||||||
strafenByLevel[l] = (strafenByLevel[l] || 0) + 1;
|
|
||||||
});
|
|
||||||
for (const level of Object.keys(aufgabenByLevel)) {
|
|
||||||
const count = strafenByLevel[level] || 0;
|
|
||||||
if (count < 1) errors.push(`Level ${level}: Keine Strafe vorhanden`);
|
|
||||||
else if (count < 2) warnings.push(`Level ${level}: Nur ${count} Strafe(n) – empfohlen ≥ 2`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.wahrscheinlichkeitSperre > 1) {
|
|
||||||
const count = content.sperren.length;
|
|
||||||
if (count < 1) errors.push('Keine Zeitstrafen vorhanden');
|
|
||||||
else if (count < 5) warnings.push(`Nur ${count} Zeitstrafe(n) – empfohlen ≥ 5`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const beteiligtGeschlecht = [...new Set((mitspieler || []).map(p => p.geschlecht).filter(Boolean))];
|
|
||||||
for (const g of beteiligtGeschlecht) {
|
|
||||||
const count = (content.finisher || []).filter(f => f.geschlecht === g).length;
|
|
||||||
if (count < 1) errors.push(`Kein Finisher für ${GESCHLECHT_LABEL[g] || g} vorhanden`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { errors, warnings };
|
|
||||||
}
|
|
||||||
|
|
||||||
function showValidation(errors, warnings, mitHinweis) {
|
|
||||||
const el = document.getElementById('message');
|
|
||||||
el.innerHTML = [
|
|
||||||
...errors.map(e => `<div>✕ ${e}</div>`),
|
|
||||||
...warnings.map(w => `<div>⚠ ${w}</div>`),
|
|
||||||
...(mitHinweis ? ['<div style="margin-top:0.5rem;font-style:italic;">Nochmals auf Spiel starten klicken um fortzufahren.</div>'] : []),
|
|
||||||
].join('');
|
|
||||||
el.className = `message ${errors.length ? 'error' : 'warning'}`;
|
|
||||||
el.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function showMessage(text, type) {
|
|
||||||
const el = document.getElementById('message');
|
|
||||||
const icon = type === 'error' ? '✕ ' : type === 'warning' ? '⚠ ' : '';
|
|
||||||
el.textContent = icon + text;
|
|
||||||
el.className = `message ${type}`;
|
|
||||||
el.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideMessage() {
|
|
||||||
document.getElementById('message').style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderToys(toys) {
|
|
||||||
const container = document.getElementById('toyList');
|
|
||||||
if (!toys.length) {
|
|
||||||
container.innerHTML = '<p class="no-toys">Keine Toys erforderlich – alle Aufgaben können gespielt werden.</p>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
container.innerHTML = toys.map(toy => {
|
|
||||||
const checked = savedToyIds === null || savedToyIds.has(toy.toyId);
|
|
||||||
return `
|
|
||||||
<label class="toy-item${checked ? ' is-checked' : ''}">
|
|
||||||
<input type="checkbox" value="${toy.toyId}"${checked ? ' checked' : ''}>
|
|
||||||
<span>
|
|
||||||
<span class="toy-item-name">${toy.name}</span>
|
|
||||||
${toy.beschreibung ? `<span class="toy-item-desc">${toy.beschreibung}</span>` : ''}
|
|
||||||
</span>
|
|
||||||
${toy.bild ? `<img class="item-img" src="data:image/png;base64,${toy.bild}" alt="">` : ''}
|
|
||||||
</label>`;
|
|
||||||
}).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function spielStarten() {
|
|
||||||
const checkedToyIds = new Set(
|
|
||||||
[...document.querySelectorAll('#toyList input[type="checkbox"]:checked')].map(cb => cb.value)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Collect full toy objects for the checked ones (for name display on overview)
|
|
||||||
const toyMap = new Map();
|
|
||||||
[...allContent.aufgaben, ...allContent.strafen, ...allContent.sperren, ...allContent.finisher].forEach(item => {
|
|
||||||
(item.benoetigteToys || []).forEach(t => toyMap.set(t.toyId, t));
|
|
||||||
});
|
|
||||||
const checkedToys = [...checkedToyIds].map(id => toyMap.get(id)).filter(Boolean);
|
|
||||||
sessionStorage.setItem('bdsm-session-toys', JSON.stringify(checkedToys));
|
|
||||||
|
|
||||||
function toyOk(item) {
|
|
||||||
const toys = item.benoetigteToys || [];
|
|
||||||
return toys.length === 0 || toys.every(t => checkedToyIds.has(t.toyId));
|
|
||||||
}
|
|
||||||
|
|
||||||
const gameContent = {
|
|
||||||
aufgaben: allContent.aufgaben.filter(toyOk),
|
|
||||||
strafen: allContent.strafen.filter(toyOk),
|
|
||||||
sperren: allContent.sperren.filter(toyOk),
|
|
||||||
finisher: allContent.finisher.filter(toyOk),
|
|
||||||
};
|
|
||||||
|
|
||||||
const settings = JSON.parse(sessionStorage.getItem('bdsm-session-settings'));
|
|
||||||
const setup = JSON.parse(sessionStorage.getItem('bdsm-session-setup'));
|
|
||||||
|
|
||||||
const btn = document.querySelector('button[onclick="spielStarten()"]');
|
|
||||||
btn.disabled = true;
|
|
||||||
|
|
||||||
// Für ACCEPTED_OWN Spieler: bereit prüfen und spielerDaten laden
|
|
||||||
const hasOwnDevice = (setup?.mitspieler || []).some(p => p.eigenesGeraet);
|
|
||||||
if (hasOwnDevice) {
|
|
||||||
const setupId = sessionStorage.getItem('bdsm-setup-id');
|
|
||||||
const einladungRes = await fetch(`/bdsm/einladung?setupId=${setupId}`);
|
|
||||||
if (einladungRes.ok) {
|
|
||||||
const einladungen = await einladungRes.json();
|
|
||||||
const nichtBereit = einladungen.filter(e => e.status === 'ACCEPTED_OWN' && !e.bereit);
|
|
||||||
if (nichtBereit.length > 0) {
|
|
||||||
showMessage('Noch nicht alle Mitspieler auf eigenem Gerät haben sich bereit erklärt.', 'error');
|
|
||||||
btn.disabled = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Spielerdaten der ACCEPTED_OWN Spieler in setup übernehmen
|
|
||||||
for (const p of setup.mitspieler) {
|
|
||||||
if (!p.eigenesGeraet || !p.einladungId) continue;
|
|
||||||
const inv = einladungen.find(e => e.einladungId === p.einladungId);
|
|
||||||
if (inv && inv.spielerDatenJson) {
|
|
||||||
const daten = JSON.parse(inv.spielerDatenJson);
|
|
||||||
p.geschlecht = daten.geschlecht;
|
|
||||||
p.spieltMit = daten.spieltMit || [];
|
|
||||||
p.rollen = daten.rollen || [];
|
|
||||||
p.werkzeuge = daten.werkzeuge || [];
|
|
||||||
p.sperrenVorFinaleAufloesen = daten.sperrenVorFinaleAufloesen !== false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { errors, warnings } = validateContent(gameContent, settings, setup?.mitspieler || []);
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
|
||||||
showValidation(errors, warnings, false);
|
|
||||||
warnungsAkzeptiert = false;
|
|
||||||
btn.disabled = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (warnings.length > 0 && !warnungsAkzeptiert) {
|
|
||||||
showValidation([], warnings, true);
|
|
||||||
warnungsAkzeptiert = true;
|
|
||||||
btn.disabled = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionStorage.setItem('bdsm-session-game', JSON.stringify(gameContent));
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. Session anlegen
|
|
||||||
const sessionRes = await fetch('/bdsm', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
wahrscheinlichkeitStrafe: settings.wahrscheinlichkeitStrafe,
|
|
||||||
wahrscheinlichkeitSperre: settings.wahrscheinlichkeitSperre,
|
|
||||||
aufgabenProLevel: settings.aufgabenProLevel,
|
|
||||||
zeitfaktorZeitstrafen: settings.zeitfaktorZeitstrafen,
|
|
||||||
setupId: sessionStorage.getItem('bdsm-setup-id'),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
if (sessionRes.status === 409) throw new Error('Du hast bereits ein laufendes BDSM-Spiel. Bitte beende es zuerst.');
|
|
||||||
if (!sessionRes.ok) throw new Error('Session konnte nicht angelegt werden.');
|
|
||||||
const location = sessionRes.headers.get('Location');
|
|
||||||
const sessionId = location.split('/').pop();
|
|
||||||
|
|
||||||
// 2. Mitspieler hinzufügen (setup bereits oben geladen und mit eigenesGeraet-Daten befüllt)
|
|
||||||
for (const p of setup.mitspieler) {
|
|
||||||
const res = await fetch(`/bdsm/${sessionId}/mitspieler`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: p.name,
|
|
||||||
geschlecht: p.geschlecht,
|
|
||||||
spieltMit: p.spieltMit,
|
|
||||||
rollen: p.rollen,
|
|
||||||
verfuegbareWerkzeuge: p.werkzeuge,
|
|
||||||
userId: p.userId || null,
|
|
||||||
eigenesGeraet: p.eigenesGeraet || false,
|
|
||||||
sperrenVorFinaleAufloesen: p.sperrenVorFinaleAufloesen !== false,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error(`Mitspieler "${p.name}" konnte nicht hinzugefügt werden.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Aufgaben setzen
|
|
||||||
const aufgabenRes = await fetch(`/bdsm/${sessionId}/aufgaben`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(gameContent),
|
|
||||||
});
|
|
||||||
if (!aufgabenRes.ok) throw new Error('Aufgaben konnten nicht gespeichert werden.');
|
|
||||||
|
|
||||||
sessionStorage.setItem('bdsm-session-id', sessionId);
|
|
||||||
// Draft löschen, da Spiel jetzt läuft
|
|
||||||
fetch('/bdsm/setup-draft', { method: 'DELETE' }).catch(() => {});
|
|
||||||
window.location.href = '/bdsmingame.html';
|
|
||||||
} catch (e) {
|
|
||||||
showMessage(e.message, 'error');
|
|
||||||
btn.disabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load all selected groups, collect content and unique toys
|
|
||||||
(async function init() {
|
|
||||||
const ok = await ladeSessionOderDraft();
|
|
||||||
if (!ok) return;
|
|
||||||
|
|
||||||
const gruppen = await Promise.all(
|
|
||||||
savedGruppen.map(id => fetch(`/gruppe/${id}`).then(r => r.ok ? r.json() : null))
|
|
||||||
);
|
|
||||||
gruppen.filter(Boolean).forEach(g => {
|
|
||||||
allContent.aufgaben.push(...(g.aufgaben || []));
|
|
||||||
allContent.strafen.push(...(g.strafen || []));
|
|
||||||
allContent.sperren.push(...(g.sperren || []));
|
|
||||||
allContent.finisher.push(...(g.finisher || []));
|
|
||||||
});
|
|
||||||
|
|
||||||
const toyMap = new Map();
|
|
||||||
[...allContent.aufgaben, ...allContent.strafen, ...allContent.sperren, ...allContent.finisher].forEach(item => {
|
|
||||||
(item.benoetigteToys || []).forEach(t => {
|
|
||||||
if (!toyMap.has(t.toyId)) toyMap.set(t.toyId, t);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
renderToys([...toyMap.values()].sort((a, b) => a.name.localeCompare(b.name)));
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -2,346 +2,10 @@
|
|||||||
<html lang="de">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
<meta http-equiv="refresh" content="0;url=/neubdsm.html">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<title>BDSM Game</title>
|
||||||
<title>BDSM Game – Warten – XXX The Game</title>
|
|
||||||
<link rel="stylesheet" href="/css/variables.css">
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
<style>
|
|
||||||
.wait-card {
|
|
||||||
text-align: center;
|
|
||||||
padding: 3rem 1rem;
|
|
||||||
}
|
|
||||||
.wait-icon { font-size: 3rem; margin-bottom: 1.5rem; animation: pulse 2s ease-in-out infinite; }
|
|
||||||
@keyframes pulse { 0%,100% { opacity:1; } 50% { opacity:0.4; } }
|
|
||||||
.wait-title { font-size: 1.3rem; font-weight: 700; margin-bottom: 0.75rem; }
|
|
||||||
.wait-sub { font-size: 0.9rem; color: var(--color-muted); line-height: 1.6; margin-bottom: 2rem; }
|
|
||||||
|
|
||||||
.setup-section { margin-bottom: 2rem; }
|
|
||||||
.setup-section h2 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
border-bottom: 1px solid var(--color-secondary);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
.card-field { margin-bottom: 1rem; }
|
|
||||||
.card-field > label { font-size: 0.8rem; color: #aaa; margin: 0 0 0.5rem 0; display: block; }
|
|
||||||
.check-group { display: flex; flex-wrap: wrap; gap: 0.5rem; }
|
|
||||||
.check-group--two-col { display: grid; grid-template-columns: 1fr 1fr; }
|
|
||||||
.check-item {
|
|
||||||
display: inline-flex; align-items: flex-start; gap: 0.45rem;
|
|
||||||
background: var(--color-secondary); border: 1px solid transparent;
|
|
||||||
border-radius: 6px; padding: 0.4rem 0.7rem;
|
|
||||||
cursor: pointer; transition: border-color 0.15s; user-select: none;
|
|
||||||
}
|
|
||||||
.check-item.is-checked { border-color: var(--color-primary); }
|
|
||||||
.check-item input { accent-color: var(--color-primary); width: auto; margin-top: 0.15rem; cursor: pointer; flex-shrink: 0; }
|
|
||||||
.check-item-label { font-size: 0.88rem; color: var(--color-text); line-height: 1.3; }
|
|
||||||
.check-item-desc { display: block; font-size: 0.72rem; color: var(--color-muted); margin-top: 0.1rem; }
|
|
||||||
.field-error { font-size: 0.78rem; color: var(--color-primary); margin-top: 0.3rem; display: none; }
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="app">
|
<body>
|
||||||
<div class="main">
|
<script>window.location.replace('/neubdsm.html');</script>
|
||||||
<!-- Konfigurations-Ansicht (für ACCEPTED_OWN Spieler) -->
|
|
||||||
<div class="content" id="configView" style="display:none;">
|
|
||||||
<h1>BDSM Game</h1>
|
|
||||||
<p style="margin-bottom:2rem;">Bitte konfiguriere deine Präferenzen, bevor das Spiel startet.</p>
|
|
||||||
|
|
||||||
<div class="setup-section">
|
|
||||||
<h2>Deine Daten</h2>
|
|
||||||
<div class="card-field">
|
|
||||||
<label>Geschlecht</label>
|
|
||||||
<div class="check-group" id="geschlechtGroup"></div>
|
|
||||||
<div class="field-error" id="geschlecht-err">Bitte Geschlecht auswählen.</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-field">
|
|
||||||
<label>Spielt mit</label>
|
|
||||||
<div class="check-group" id="spieltMitGroup"></div>
|
|
||||||
<div class="field-error" id="spieltmit-err">Bitte mindestens eine Option wählen.</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-field">
|
|
||||||
<label>Rollen</label>
|
|
||||||
<div class="check-group" id="rollenGroup"></div>
|
|
||||||
<div class="field-error" id="rollen-err">Bitte mindestens eine Rolle wählen.</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-field">
|
|
||||||
<label>Verfügbar</label>
|
|
||||||
<div class="check-group check-group--two-col" id="werkzeugeGroup"></div>
|
|
||||||
<div class="field-error" id="werkzeuge-err">Bitte mindestens ein Werkzeug wählen.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="setup-section">
|
|
||||||
<h2>Finale</h2>
|
|
||||||
<div class="card-field">
|
|
||||||
<label class="check-item is-checked" id="sperreLabel">
|
|
||||||
<input type="checkbox" id="sperrenAufloesen" checked onchange="toggleSperreWarn()">
|
|
||||||
<span class="check-item-label">Zeitstrafen vor dem Finale auflösen</span>
|
|
||||||
</label>
|
|
||||||
<div style="display:none; margin-top:0.4rem; font-size:0.78rem; color:var(--color-primary);" id="sperreWarn">
|
|
||||||
⚠️ Hinweis: Zeitstrafen werden nicht aufgelöst. Du könntest im Finale leer ausgehen.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="message" id="configMessage" style="display:none;"></div>
|
|
||||||
<button onclick="bereitMachen()" style="width:100%;">Bereit</button>
|
|
||||||
<button class="secondary" style="width:100%; margin-top:0.75rem;" onclick="abbrechen()">Abbrechen</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Warte-Ansicht -->
|
|
||||||
<div class="content wait-card" id="waitView" style="display:none;">
|
|
||||||
<div class="wait-icon">⏳</div>
|
|
||||||
<div class="wait-title">Warte auf Spielstart…</div>
|
|
||||||
<div class="wait-sub" id="sub">Der Host startet das Spiel in Kürze. Diese Seite aktualisiert sich automatisch.</div>
|
|
||||||
<div class="message" id="message" style="display:none;"></div>
|
|
||||||
<button class="secondary" onclick="abbrechen()">Abbrechen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script src="/js/sidebar.js"></script>
|
|
||||||
<script>
|
|
||||||
const params = new URLSearchParams(location.search);
|
|
||||||
const einladungId = params.get('id');
|
|
||||||
if (!einladungId) window.location.replace('/userhome.html');
|
|
||||||
|
|
||||||
const GESCHLECHTER = [
|
|
||||||
{ value: 'MAENNLICH', label: 'Männlich' },
|
|
||||||
{ value: 'WEIBLICH', label: 'Weiblich' },
|
|
||||||
{ value: 'DIVERS', label: 'Divers' },
|
|
||||||
];
|
|
||||||
const ROLLEN = [
|
|
||||||
{ value: 'AUFGABE_AKTIV', label: 'Aufgabe – Aktiv' },
|
|
||||||
{ value: 'AUFGABE_PASSIV', label: 'Aufgabe – Passiv' },
|
|
||||||
{ value: 'BESTRAFUNG_AKTIV', label: 'Bestrafung – Aktiv' },
|
|
||||||
{ value: 'BESTRAFUNG_PASSIV', label: 'Bestrafung – Passiv' },
|
|
||||||
];
|
|
||||||
const WERKZEUGE = [
|
|
||||||
{ value: 'MUND', label: 'Mund', desc: 'Gewillt den Mund einzusetzen' },
|
|
||||||
{ value: 'VAGINA', label: 'Vagina', desc: 'Verfügt über eine Vagina und setzt sie ein' },
|
|
||||||
{ value: 'PENIS', label: 'Penis', desc: 'Verfügt über einen Penis und setzt ihn ein' },
|
|
||||||
{ value: 'ANUS', label: 'Anus', desc: 'Gewillt den Anus einzusetzen' },
|
|
||||||
{ value: 'UMSCHNALLDILDO', label: 'Umschnall-Dildo', desc: 'Verfügt über einen Umschnall-Dildo' },
|
|
||||||
];
|
|
||||||
const WERKZEUGE_DEFAULTS = {
|
|
||||||
MAENNLICH: ['MUND', 'PENIS', 'ANUS', 'UMSCHNALLDILDO'],
|
|
||||||
WEIBLICH: ['MUND', 'VAGINA', 'ANUS', 'UMSCHNALLDILDO'],
|
|
||||||
DIVERS: ['MUND', 'ANUS', 'UMSCHNALLDILDO'],
|
|
||||||
};
|
|
||||||
|
|
||||||
function buildCheckItems(containerId, items, type) {
|
|
||||||
const container = document.getElementById(containerId);
|
|
||||||
container.innerHTML = items.map(({ value, label, desc }) => `
|
|
||||||
<label class="check-item">
|
|
||||||
<input type="${type}" name="${containerId}" value="${value}">
|
|
||||||
<span>
|
|
||||||
<span class="check-item-label">${label}</span>
|
|
||||||
${desc ? `<span class="check-item-desc">${desc}</span>` : ''}
|
|
||||||
</span>
|
|
||||||
</label>`).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
function initForm() {
|
|
||||||
buildCheckItems('geschlechtGroup', GESCHLECHTER, 'radio');
|
|
||||||
buildCheckItems('spieltMitGroup', GESCHLECHTER, 'checkbox');
|
|
||||||
buildCheckItems('rollenGroup', ROLLEN, 'checkbox');
|
|
||||||
buildCheckItems('werkzeugeGroup', WERKZEUGE, 'checkbox');
|
|
||||||
|
|
||||||
document.addEventListener('change', e => {
|
|
||||||
const input = e.target;
|
|
||||||
if (input.type !== 'checkbox' && input.type !== 'radio') return;
|
|
||||||
if (input.type === 'radio') {
|
|
||||||
document.querySelectorAll(`input[name="${input.name}"]`).forEach(r =>
|
|
||||||
r.closest('.check-item')?.classList.toggle('is-checked', r.checked));
|
|
||||||
if (input.checked && input.name === 'geschlechtGroup') {
|
|
||||||
const defaults = WERKZEUGE_DEFAULTS[input.value] || [];
|
|
||||||
document.querySelectorAll('input[name="werkzeugeGroup"]').forEach(cb => {
|
|
||||||
cb.checked = defaults.includes(cb.value);
|
|
||||||
cb.closest('.check-item')?.classList.toggle('is-checked', cb.checked);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
input.closest('.check-item')?.classList.toggle('is-checked', input.checked);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Pre-fill from profile + bdsm-defaults
|
|
||||||
Promise.all([
|
|
||||||
fetch('/login/me').then(r => r.ok ? r.json() : null).catch(() => null),
|
|
||||||
fetch('/user/me/bdsm-defaults').then(r => r.ok ? r.json() : null).catch(() => null),
|
|
||||||
]).then(([user, bdsmDefaults]) => {
|
|
||||||
if (user?.geschlecht) {
|
|
||||||
const radio = document.querySelector(`input[name="geschlechtGroup"][value="${user.geschlecht}"]`);
|
|
||||||
if (radio) {
|
|
||||||
radio.checked = true;
|
|
||||||
radio.closest('.check-item')?.classList.add('is-checked');
|
|
||||||
const defaults = (bdsmDefaults?.werkzeuge?.length > 0)
|
|
||||||
? bdsmDefaults.werkzeuge
|
|
||||||
: (WERKZEUGE_DEFAULTS[user.geschlecht] || []);
|
|
||||||
document.querySelectorAll('input[name="werkzeugeGroup"]').forEach(cb => {
|
|
||||||
cb.checked = defaults.includes(cb.value);
|
|
||||||
cb.closest('.check-item')?.classList.toggle('is-checked', cb.checked);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bdsmDefaults?.spieltMit?.length > 0) {
|
|
||||||
bdsmDefaults.spieltMit.forEach(val => {
|
|
||||||
const cb = document.querySelector(`input[name="spieltMitGroup"][value="${val}"]`);
|
|
||||||
if (cb) { cb.checked = true; cb.closest('.check-item')?.classList.add('is-checked'); }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (bdsmDefaults?.rollen?.length > 0) {
|
|
||||||
bdsmDefaults.rollen.forEach(val => {
|
|
||||||
const cb = document.querySelector(`input[name="rollenGroup"][value="${val}"]`);
|
|
||||||
if (cb) { cb.checked = true; cb.closest('.check-item')?.classList.add('is-checked'); }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getChecked(name) {
|
|
||||||
return [...document.querySelectorAll(`input[name="${name}"]:checked`)].map(el => el.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleSperreWarn() {
|
|
||||||
const cb = document.getElementById('sperrenAufloesen');
|
|
||||||
const warn = document.getElementById('sperreWarn');
|
|
||||||
const label = document.getElementById('sperreLabel');
|
|
||||||
if (warn) warn.style.display = cb.checked ? 'none' : 'block';
|
|
||||||
if (label) label.classList.toggle('is-checked', cb.checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setFieldError(id, show) {
|
|
||||||
const el = document.getElementById(id);
|
|
||||||
if (el) el.style.display = show ? 'block' : 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function bereitMachen() {
|
|
||||||
const geschlecht = getChecked('geschlechtGroup');
|
|
||||||
const spieltMit = getChecked('spieltMitGroup');
|
|
||||||
const rollen = getChecked('rollenGroup');
|
|
||||||
const werkzeuge = getChecked('werkzeugeGroup');
|
|
||||||
|
|
||||||
setFieldError('geschlecht-err', geschlecht.length === 0);
|
|
||||||
setFieldError('spieltmit-err', spieltMit.length === 0);
|
|
||||||
setFieldError('rollen-err', rollen.length === 0);
|
|
||||||
setFieldError('werkzeuge-err', werkzeuge.length === 0);
|
|
||||||
|
|
||||||
if (!geschlecht.length || !spieltMit.length || !rollen.length || !werkzeuge.length) return;
|
|
||||||
|
|
||||||
const sperrenAufloesen = document.getElementById('sperrenAufloesen');
|
|
||||||
const spielerDatenJson = JSON.stringify({
|
|
||||||
geschlecht: geschlecht[0],
|
|
||||||
spieltMit,
|
|
||||||
rollen,
|
|
||||||
werkzeuge,
|
|
||||||
sperrenVorFinaleAufloesen: sperrenAufloesen ? sperrenAufloesen.checked : true,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch(`/bdsm/einladung/${einladungId}/spielerdaten`, {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ spielerDatenJson }),
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error();
|
|
||||||
document.getElementById('configView').style.display = 'none';
|
|
||||||
document.getElementById('waitView').style.display = 'block';
|
|
||||||
// Sofort prüfen + Polling starten
|
|
||||||
pruefen();
|
|
||||||
pollInterval = setInterval(pruefen, 3000);
|
|
||||||
} catch (_) {
|
|
||||||
const el = document.getElementById('configMessage');
|
|
||||||
el.textContent = 'Fehler beim Speichern. Bitte erneut versuchen.';
|
|
||||||
el.className = 'message error';
|
|
||||||
el.style.display = 'block';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let pollInterval = null;
|
|
||||||
|
|
||||||
async function pruefen() {
|
|
||||||
try {
|
|
||||||
const res = await fetch(`/bdsm/einladung/${einladungId}`);
|
|
||||||
if (!res.ok) return;
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
if (data.status === 'CANCELLED') {
|
|
||||||
stopPoll();
|
|
||||||
zeigeFehler('Die Einladung wurde abgebrochen.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.sessionId) {
|
|
||||||
try {
|
|
||||||
const mRes = await fetch(`/bdsm/${data.sessionId}/mitspieler/me`);
|
|
||||||
if (mRes.status === 200) {
|
|
||||||
const mData = await mRes.json();
|
|
||||||
sessionStorage.setItem('bdsm-guest-mitspieler-id', mData.mitspielerId);
|
|
||||||
sessionStorage.setItem('bdsm-guest-name', mData.name);
|
|
||||||
sessionStorage.setItem('bdsm-session-id', data.sessionId);
|
|
||||||
sessionStorage.setItem('bdsm-is-guest', 'true');
|
|
||||||
stopPoll();
|
|
||||||
window.location.replace('/bdsmingame.html');
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopPoll() {
|
|
||||||
if (pollInterval) { clearInterval(pollInterval); pollInterval = null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
function zeigeFehler(text) {
|
|
||||||
document.getElementById('waitView').style.display = 'block';
|
|
||||||
document.getElementById('configView').style.display = 'none';
|
|
||||||
document.getElementById('sub').style.display = 'none';
|
|
||||||
const el = document.getElementById('message');
|
|
||||||
el.textContent = text;
|
|
||||||
el.className = 'message error';
|
|
||||||
el.style.display = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function abbrechen() {
|
|
||||||
stopPoll();
|
|
||||||
await fetch(`/bdsm/einladung/${einladungId}`, { method: 'DELETE' }).catch(() => {});
|
|
||||||
window.location.href = '/userhome.html';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init: check if already bereit or if config needed
|
|
||||||
async function init() {
|
|
||||||
try {
|
|
||||||
const res = await fetch(`/bdsm/einladung/${einladungId}`);
|
|
||||||
if (!res.ok) { window.location.replace('/userhome.html'); return; }
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
if (data.status === 'CANCELLED') {
|
|
||||||
document.getElementById('waitView').style.display = 'block';
|
|
||||||
zeigeFehler('Die Einladung wurde abgebrochen.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.status === 'ACCEPTED_OWN' && !data.bereit) {
|
|
||||||
// Show config form
|
|
||||||
initForm();
|
|
||||||
document.getElementById('configView').style.display = 'block';
|
|
||||||
// Don't start polling yet – user must submit form first
|
|
||||||
} else {
|
|
||||||
// Already bereit or ACCEPTED_HOST → show waiting screen + start poll
|
|
||||||
document.getElementById('waitView').style.display = 'block';
|
|
||||||
pruefen();
|
|
||||||
pollInterval = setInterval(pruefen, 3000);
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
window.location.replace('/userhome.html');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
init();
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -837,7 +837,7 @@
|
|||||||
closeBdsmInviteDialog();
|
closeBdsmInviteDialog();
|
||||||
removeRecvItem(key);
|
removeRecvItem(key);
|
||||||
if (mode === 'OWN_DEVICE') {
|
if (mode === 'OWN_DEVICE') {
|
||||||
window.location.href = `/bdsmwarten.html?id=${key}`;
|
window.location.href = `/neubdsm.html`;
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
errEl.textContent = 'Fehler beim Speichern der Antwort.';
|
errEl.textContent = 'Fehler beim Speichern der Antwort.';
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
label: 'BDSM Game',
|
label: 'BDSM Game',
|
||||||
icon: '◆',
|
icon: '◆',
|
||||||
items: [
|
items: [
|
||||||
{ href: '/bdsm.html', icon: '▷', label: 'Neue Session', id: 'navBdsmNeu' },
|
{ href: '/neubdsm.html', icon: '▷', label: 'Neue Session', id: 'navBdsmNeu' },
|
||||||
{ href: '#', icon: '⏳', label: 'Aktive Session', id: 'navBdsmAktiv' },
|
{ href: '#', icon: '⏳', label: 'Aktive Session', id: 'navBdsmAktiv' },
|
||||||
{ href: '/bdsmingame.html', icon: '▶', label: 'Im Spiel', id: 'navBdsmImSpiel' },
|
{ href: '/bdsmingame.html', icon: '▶', label: 'Im Spiel', id: 'navBdsmImSpiel' },
|
||||||
{ href: '/aufgaben.html', icon: '✓', label: 'Aufgaben' },
|
{ href: '/aufgaben.html', icon: '✓', label: 'Aufgaben' },
|
||||||
@@ -118,7 +118,7 @@
|
|||||||
navAktiv.style.display = '';
|
navAktiv.style.display = '';
|
||||||
const ziel = aktiv.sessionId
|
const ziel = aktiv.sessionId
|
||||||
? '/bdsmingame.html'
|
? '/bdsmingame.html'
|
||||||
: `/bdsmwarten.html?id=${aktiv.einladungId}`;
|
: `/neubdsm.html`;
|
||||||
navAktiv.querySelector('a').href = ziel;
|
navAktiv.querySelector('a').href = ziel;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -50,14 +50,6 @@
|
|||||||
if (e.key === 'Enter') login();
|
if (e.key === 'Enter') login();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function sha256(text) {
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
const data = encoder.encode(text);
|
|
||||||
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
||||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
||||||
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function login() {
|
async function login() {
|
||||||
const email = document.getElementById('email').value.trim();
|
const email = document.getElementById('email').value.trim();
|
||||||
const password = document.getElementById('password').value;
|
const password = document.getElementById('password').value;
|
||||||
@@ -73,9 +65,11 @@
|
|||||||
hideMessage();
|
hideMessage();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const hash = await sha256(password);
|
const response = await fetch('/login', {
|
||||||
const url = `/login?email=${encodeURIComponent(email)}&hash=${encodeURIComponent(hash)}`;
|
method: 'POST',
|
||||||
const response = await fetch(url, { method: 'GET' });
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ email, password })
|
||||||
|
});
|
||||||
|
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
const user = await response.json();
|
const user = await response.json();
|
||||||
|
|||||||
1484
xxxthegame/src/main/resources/static/neubdsm.html
Normal file
1484
xxxthegame/src/main/resources/static/neubdsm.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -42,14 +42,6 @@
|
|||||||
if (e.key === 'Enter') register();
|
if (e.key === 'Enter') register();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function sha256(text) {
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
const data = encoder.encode(text);
|
|
||||||
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
||||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
||||||
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function register() {
|
async function register() {
|
||||||
const name = document.getElementById('name').value.trim();
|
const name = document.getElementById('name').value.trim();
|
||||||
const email = document.getElementById('email').value.trim();
|
const email = document.getElementById('email').value.trim();
|
||||||
@@ -84,11 +76,10 @@
|
|||||||
hideMessage();
|
hideMessage();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const passwordHash = await sha256(password);
|
|
||||||
const response = await fetch('/registration', {
|
const response = await fetch('/registration', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ name, email, passwordHash, geburtsdatum })
|
body: JSON.stringify({ name, email, password, geburtsdatum })
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status === 202) {
|
if (response.status === 202) {
|
||||||
|
|||||||
@@ -78,14 +78,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function sha256(text) {
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
const data = encoder.encode(text);
|
|
||||||
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
||||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
||||||
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
const password = document.getElementById('password').value;
|
const password = document.getElementById('password').value;
|
||||||
const passwordConfirm = document.getElementById('passwordConfirm').value;
|
const passwordConfirm = document.getElementById('passwordConfirm').value;
|
||||||
@@ -110,11 +102,10 @@
|
|||||||
hideMessage();
|
hideMessage();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const passwordHash = await sha256(password);
|
|
||||||
const response = await fetch('/password-reset/confirm', {
|
const response = await fetch('/password-reset/confirm', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ token, passwordHash })
|
body: JSON.stringify({ token, password })
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
Tauche ein in strukturierte Sessions mit Aufgaben, Toys und klaren Rollen.
|
Tauche ein in strukturierte Sessions mit Aufgaben, Toys und klaren Rollen.
|
||||||
Definiere Grenzen, vergib Aufgaben und erlebe intensive Momente mit deinem Partner.
|
Definiere Grenzen, vergib Aufgaben und erlebe intensive Momente mit deinem Partner.
|
||||||
</p>
|
</p>
|
||||||
<a href="/bdsm.html"><button class="game-card-btn">Neue Session starten</button></a>
|
<a href="/neubdsm.html"><button class="game-card-btn">Neue Session starten</button></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="game-card">
|
<div class="game-card">
|
||||||
|
|||||||
Reference in New Issue
Block a user