diff --git a/.claude/settings.local.json b/.claude/settings.local.json index bdc24da..3cb3811 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -72,7 +72,8 @@ "Bash(mv bdsm-einladung.html games/bdsm/)", "Bash(mv bdsmingame.html games/bdsm/)", "Bash(mv bdsmplayers.html games/bdsm/)", - "Bash(perl -pi -e 's|\\\\.requestMatchers\\\\\\(\"\"/\\\\*\\\\.html\"\"\\\\\\)\\\\.permitAll\\\\\\(\\\\\\)|.requestMatchers\\(\"\"/*.html\"\"\\).permitAll\\(\\)\\\\n .requestMatchers\\(\"\"/**/*.html\"\"\\).permitAll\\(\\)|' /home/mario/Workspaces/xxx-thegame/xxxthegame/src/main/java/de/oaa/xxx/config/SecurityConfig.java)" + "Bash(perl -pi -e 's|\\\\.requestMatchers\\\\\\(\"\"/\\\\*\\\\.html\"\"\\\\\\)\\\\.permitAll\\\\\\(\\\\\\)|.requestMatchers\\(\"\"/*.html\"\"\\).permitAll\\(\\)\\\\n .requestMatchers\\(\"\"/**/*.html\"\"\\).permitAll\\(\\)|' /home/mario/Workspaces/xxx-thegame/xxxthegame/src/main/java/de/oaa/xxx/config/SecurityConfig.java)", + "Bash(./gradlew compileJava -x test)" ] } } diff --git a/.metadata/.lock_info b/.metadata/.lock_info index 58ef72a..b679ee7 100644 --- a/.metadata/.lock_info +++ b/.metadata/.lock_info @@ -1,5 +1,5 @@ -#Mon Mar 30 07:33:01 CEST 2026 +#Tue Mar 31 20:02:52 CEST 2026 display=\:0 host=mario-mint -process-id=2955 +process-id=9888 user=mario diff --git a/.metadata/.log b/.metadata/.log index 83d2901..df5d3fa 100644 --- a/.metadata/.log +++ b/.metadata/.log @@ -1379,3 +1379,177 @@ Binding(CTRL+SHIFT+T, ,,true),null), org.eclipse.ui.defaultAcceleratorConfiguration, org.eclipse.ui.contexts.window,,,system) + +!ENTRY org.springframework.tooling.boot.ls 1 0 2026-03-30 22:56:58.269 +!MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS +!SESSION 2026-03-31 08:24:30.017 ----------------------------------------------- +eclipse.buildId=4.39.0.20260305-0817 +java.version=21.0.6 +java.vendor=Eclipse Adoptium +BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=de_DE +Framework arguments: -product org.eclipse.epp.package.java.product +Command-line arguments: -os linux -ws gtk -arch x86_64 -clean -product org.eclipse.epp.package.java.product + +!ENTRY ch.qos.logback.classic 1 0 2026-03-31 08:24:31.551 +!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized. + +!ENTRY ch.qos.logback.classic 1 0 2026-03-31 08:24:34.912 +!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-31 08:24:35.071 +!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-31 08:24:35.071 +!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-31 08:24:35.221 +!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-31 08:24:35.221 +!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory' + +!ENTRY org.eclipse.jface 2 0 2026-03-31 09:50:47.663 +!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation. +!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-31 09:50:47.663 +!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-31 10:19:59.888 +!MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS +!SESSION 2026-03-31 11:33:04.112 ----------------------------------------------- +eclipse.buildId=4.39.0.20260305-0817 +java.version=21.0.6 +java.vendor=Eclipse Adoptium +BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=de_DE +Framework arguments: -product org.eclipse.epp.package.java.product +Command-line arguments: -os linux -ws gtk -arch x86_64 -clean -product org.eclipse.epp.package.java.product + +!ENTRY ch.qos.logback.classic 1 0 2026-03-31 11:33:05.636 +!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized. + +!ENTRY ch.qos.logback.classic 1 0 2026-03-31 11:33:15.062 +!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-31 11:33:15.221 +!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-31 11:33:15.221 +!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-31 11:33:15.366 +!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-31 11:33:15.366 +!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory' + +!ENTRY org.eclipse.jface 2 0 2026-03-31 11:35:22.321 +!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation. +!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-31 11:35:22.321 +!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-31 15:25:37.605 +!MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS +!SESSION 2026-03-31 20:02:49.357 ----------------------------------------------- +eclipse.buildId=4.39.0.20260305-0817 +java.version=21.0.6 +java.vendor=Eclipse Adoptium +BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=de_DE +Framework arguments: -product org.eclipse.epp.package.java.product +Command-line arguments: -os linux -ws gtk -arch x86_64 -clean -product org.eclipse.epp.package.java.product + +!ENTRY ch.qos.logback.classic 1 0 2026-03-31 20:02:50.906 +!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized. + +!ENTRY ch.qos.logback.classic 1 0 2026-03-31 20:02:53.313 +!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-31 20:02:53.439 +!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-31 20:02:53.440 +!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-31 20:02:53.586 +!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-31 20:02:53.586 +!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory' + +!ENTRY org.eclipse.jface 2 0 2026-03-31 20:48:38.922 +!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation. +!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-31 20:48:38.922 +!MESSAGE A conflict occurred for CTRL+SHIFT+T: +Binding(CTRL+SHIFT+T, + ParameterizedCommand(Command(org.eclipse.jdt.ui.navigate.open.type,Open Type, + Open a type in a Java editor, + Category(org.eclipse.ui.category.navigate,Navigate,null,true), + WorkbenchHandlerServiceHandler("org.eclipse.jdt.ui.navigate.open.type"), + ,,true),null), + org.eclipse.ui.defaultAcceleratorConfiguration, + org.eclipse.ui.contexts.window,,,system) +Binding(CTRL+SHIFT+T, + ParameterizedCommand(Command(org.eclipse.lsp4e.symbolInWorkspace,Go to Symbol in Workspace, + , + Category(org.eclipse.lsp4e.category,Language Servers,null,true), + WorkbenchHandlerServiceHandler("org.eclipse.lsp4e.symbolInWorkspace"), + ,,true),null), + org.eclipse.ui.defaultAcceleratorConfiguration, + org.eclipse.ui.contexts.window,,,system) + +!ENTRY org.eclipse.lsp4e 2 0 2026-03-31 21:38:55.972 +!MESSAGE Javadoc unavailable. Failed to obtain it. +!STACK 0 +java.lang.InterruptedException + at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:386) + at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2096) + at org.eclipse.lsp4e.jdt.LSJavaHoverProvider.getHoverInfo2(LSJavaHoverProvider.java:66) + at org.eclipse.jdt.internal.ui.text.java.hover.BestMatchHover.getHoverInfo2(BestMatchHover.java:165) + at org.eclipse.jdt.internal.ui.text.java.hover.BestMatchHover.getHoverInfo2(BestMatchHover.java:131) + at org.eclipse.jdt.internal.ui.text.java.hover.JavaEditorTextHoverProxy.getHoverInfo2(JavaEditorTextHoverProxy.java:89) + at org.eclipse.jface.text.TextViewerHoverManager$1.run(TextViewerHoverManager.java:155) + +!ENTRY org.eclipse.jface 2 0 2026-03-31 22:11:43.432 +!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation. +!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-31 22:11:43.432 +!MESSAGE A conflict occurred for CTRL+R: +Binding(CTRL+R, + ParameterizedCommand(Command(org.eclipse.debug.ui.commands.RunToLine,Run to Line, + Resume and break when execution reaches the current line, + Category(org.eclipse.debug.ui.category.run,Run/Debug,Run/Debug command category,true), + WorkbenchHandlerServiceHandler("org.eclipse.debug.ui.commands.RunToLine"), + ,,true),null), + org.eclipse.ui.defaultAcceleratorConfiguration, + org.eclipse.debug.ui.debugging,,,system) +Binding(CTRL+R, + ParameterizedCommand(Command(org.springframework.ide.eclipse.boot.restart.commands.restart,Trigger Restart, + Restart Spring Boot Application, + Category(org.eclipse.debug.ui.category.run,Run/Debug,Run/Debug command category,true), + WorkbenchHandlerServiceHandler("org.springframework.ide.eclipse.boot.restart.commands.restart"), + ,,true),null), + org.eclipse.ui.defaultAcceleratorConfiguration, + org.eclipse.debug.ui.console,,,system) diff --git a/.metadata/.plugins/org.eclipse.buildship.core/gradle/versions.json b/.metadata/.plugins/org.eclipse.buildship.core/gradle/versions.json index c47ab07..02cfec6 100644 --- a/.metadata/.plugins/org.eclipse.buildship.core/gradle/versions.json +++ b/.metadata/.plugins/org.eclipse.buildship.core/gradle/versions.json @@ -1,24 +1,7 @@ [ { - "version" : "9.6.0-20260329003549+0000", - "buildTime" : "20260329003549+0000", - "commitId" : "db62c2f2b404217cb6a7eef2598c6e84ab08fa27", - "current" : false, - "snapshot" : true, - "nightly" : true, - "releaseNightly" : false, - "activeRc" : false, - "rcFor" : "", - "milestoneFor" : "", - "broken" : false, - "downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260329003549+0000-bin.zip", - "checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260329003549+0000-bin.zip.sha256", - "checksum" : "27b9c08aeaf720b9ee44dc6eef5543699bafba27772aa8a33e64cf964a2ea958", - "wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260329003549+0000-wrapper.jar.sha256", - "wrapperChecksum" : "497c8c2a7e5031f6aa847f88104aa80a93532ec32ee17bdb8d1d2f67a194a9c7" -}, { - "version" : "9.5.0-20260328024422+0000", - "buildTime" : "20260328024422+0000", - "commitId" : "a90300e5c547f6d0416d765f1ef285d1ecb589f9", + "version" : "9.5.0-20260331054436+0000", + "buildTime" : "20260331054436+0000", + "commitId" : "04cdc7917382feb3229f13b035ea48f106ad01f1", "current" : false, "snapshot" : true, "nightly" : false, @@ -27,10 +10,44 @@ "rcFor" : "", "milestoneFor" : "", "broken" : false, - "downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260328024422+0000-bin.zip", - "checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260328024422+0000-bin.zip.sha256", - "checksum" : "f921ed9b701b2046ba53c3c499df12ebab7b70b6d58c83337d9375427f9af2ee", - "wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260328024422+0000-wrapper.jar.sha256", + "downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260331054436+0000-bin.zip", + "checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260331054436+0000-bin.zip.sha256", + "checksum" : "4ab20ff318524006769da0e39fa7cf8f355a5ca54ea213dd2edadd9019d95649", + "wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260331054436+0000-wrapper.jar.sha256", + "wrapperChecksum" : "497c8c2a7e5031f6aa847f88104aa80a93532ec32ee17bdb8d1d2f67a194a9c7" +}, { + "version" : "9.6.0-20260331012943+0000", + "buildTime" : "20260331012943+0000", + "commitId" : "6921c9df28f41760c3a348e57a9bf332d093742e", + "current" : false, + "snapshot" : true, + "nightly" : true, + "releaseNightly" : false, + "activeRc" : false, + "rcFor" : "", + "milestoneFor" : "", + "broken" : false, + "downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260331012943+0000-bin.zip", + "checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260331012943+0000-bin.zip.sha256", + "checksum" : "83f35f9ee38851b1835842ba93b9846fc43be77792c2cae138570c5f262039e0", + "wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260331012943+0000-wrapper.jar.sha256", + "wrapperChecksum" : "497c8c2a7e5031f6aa847f88104aa80a93532ec32ee17bdb8d1d2f67a194a9c7" +}, { + "version" : "9.5.0-rc-1", + "buildTime" : "20260330120715+0000", + "commitId" : "6a1704c113de068f7e9a6744245c7eb4bc5091d0", + "current" : false, + "snapshot" : false, + "nightly" : false, + "releaseNightly" : false, + "activeRc" : true, + "rcFor" : "9.5.0", + "milestoneFor" : "", + "broken" : false, + "downloadUrl" : "https://services.gradle.org/distributions/gradle-9.5.0-rc-1-bin.zip", + "checksumUrl" : "https://services.gradle.org/distributions/gradle-9.5.0-rc-1-bin.zip.sha256", + "checksum" : "66d79b10eb939c954bf1ac3be9d9cde985301b56058d49542286c35782ae1e74", + "wrapperChecksumUrl" : "https://services.gradle.org/distributions/gradle-9.5.0-rc-1-wrapper.jar.sha256", "wrapperChecksum" : "497c8c2a7e5031f6aa847f88104aa80a93532ec32ee17bdb8d1d2f67a194a9c7" }, { "version" : "9.4.1", diff --git a/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources b/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources index 62cdb19..539e0f2 100644 Binary files a/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources and b/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources differ diff --git a/.metadata/.plugins/org.eclipse.e4.workbench/workbench.xmi b/.metadata/.plugins/org.eclipse.e4.workbench/workbench.xmi index fcc3222..ab85808 100644 --- a/.metadata/.plugins/org.eclipse.e4.workbench/workbench.xmi +++ b/.metadata/.plugins/org.eclipse.e4.workbench/workbench.xmi @@ -1,8 +1,8 @@ - - + + activeSchemeId:org.eclipse.ui.defaultAcceleratorConfiguration - + @@ -11,9 +11,9 @@ topLevel shellMaximized - - - + + + persp.actionSet:org.eclipse.mylyn.tasks.ui.navigation persp.actionSet:org.eclipse.ui.cheatsheets.actionSet @@ -84,132 +84,132 @@ persp.editorOnboardingCommand:Show Key Assist$$$Shift+Ctrl+L persp.editorOnboardingCommand:New$$$Ctrl+N persp.editorOnboardingCommand:Open Type$$$Shift+Ctrl+T - - - - + + + + org.eclipse.e4.primaryNavigationStack - + active + noFocus + View categoryTag:Java - + View categoryTag:Java - + View categoryTag:General - + View categoryTag:Java - + Minimized - + View categoryTag:Spring - - + + View categoryTag:Git - - - - + + + + org.eclipse.e4.secondaryNavigationStack Minimized - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Mylyn - + View categoryTag:Java - + View categoryTag:Ant - + org.eclipse.e4.secondaryDataStack Oomph Gradle Debug Version Control (Team) - active - noFocus - + View categoryTag:General - + View categoryTag:Java - + View categoryTag:Java - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Terminal - + View categoryTag:Gradle - + View categoryTag:Gradle - + View categoryTag:Debug - + View categoryTag:Version Control (Team) - + View categoryTag:Oomph NoRestore @@ -218,7 +218,7 @@ - + persp.actionSet:org.eclipse.mylyn.tasks.ui.navigation persp.actionSet:org.eclipse.ui.cheatsheets.actionSet @@ -267,99 +267,99 @@ persp.editorOnboardingCommand:Step Over$$$F6 persp.editorOnboardingCommand:Step Return$$$F7 persp.editorOnboardingCommand:Resume$$$F8 - - + + org.eclipse.e4.primaryNavigationStack - + View categoryTag:Debug - + View categoryTag:General - + View categoryTag:Java - + View categoryTag:Java - + View categoryTag:Java - - - - + + + + org.eclipse.e4.secondaryNavigationStack - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Ant - - + + View categoryTag:General - + View categoryTag:General - + View categoryTag:Debug - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Terminal - + View categoryTag:Debug - + View categoryTag:General @@ -368,2718 +368,2725 @@ - - + + View categoryTag:Help - + View categoryTag:General - + View categoryTag:Help - + View categoryTag:Help - + View categoryTag:General - + ViewMenu menuContribution:menu - + - + View categoryTag:Help - - + + EditorStack - - + + + Editor + removeOnHide + SpringBootPropertyEditor + + + + Editor + removeOnHide + org.eclipse.ui.DefaultTextEditor + + + Editor removeOnHide org.eclipse.jdt.ui.CompilationUnitEditor - - + + Editor removeOnHide org.eclipse.jdt.ui.CompilationUnitEditor - - + + Editor removeOnHide org.eclipse.jdt.ui.CompilationUnitEditor - - + + Editor removeOnHide org.eclipse.jdt.ui.CompilationUnitEditor - - + + Editor removeOnHide org.eclipse.jdt.ui.CompilationUnitEditor - - + + Editor removeOnHide org.eclipse.jdt.ui.CompilationUnitEditor - - + + Editor removeOnHide - org.eclipse.ui.genericeditor.GenericEditor + org.eclipse.jdt.ui.CompilationUnitEditor - - - Editor - removeOnHide - org.eclipse.ui.genericeditor.GenericEditor - - - + + Editor removeOnHide org.eclipse.jdt.ui.CompilationUnitEditor - + View categoryTag:Java - + active + ViewMenu menuContribution:menu - + - + View categoryTag:Java - + View categoryTag:General - + View categoryTag:General - + ViewMenu menuContribution:menu - + - + View categoryTag:Java - + View categoryTag:Java - + - + View categoryTag:General - + ViewMenu menuContribution:menu - + - + View categoryTag:General - active - + ViewMenu menuContribution:menu - + - + View categoryTag:General - + View categoryTag:General - + ViewMenu menuContribution:menu - + - + View categoryTag:General - + ViewMenu menuContribution:menu - + - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Mylyn - + View categoryTag:Terminal - + View categoryTag:Java - + View categoryTag:Git - + View categoryTag:Java - + View categoryTag:Spring - + ViewMenu menuContribution:menu - + - + View categoryTag:Ant - + View categoryTag:Gradle - + ViewMenu menuContribution:menu - + - + View categoryTag:Gradle - + ViewMenu menuContribution:menu - + - + View categoryTag:Debug - + busy + ViewMenu menuContribution:menu - + - + View categoryTag:Debug - + View categoryTag:Debug - + ViewMenu menuContribution:menu - + - + View categoryTag:Debug - + ViewMenu menuContribution:menu - + - + View categoryTag:Debug - + ViewMenu menuContribution:menu - + - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Debug - + ViewMenu menuContribution:menu - + - + View categoryTag:Version Control (Team) - + ViewMenu menuContribution:menu - + - + View categoryTag:Oomph NoRestore - + ViewMenu menuContribution:menu - + - - + + toolbarSeparator - + - + Draggable - + - + toolbarSeparator - + - + Draggable - - + + - + toolbarSeparator - + - + Draggable - + Draggable - + Draggable - + Draggable - + toolbarSeparator - + - + Draggable - + - + Draggable - + Draggable - + toolbarSeparator - + - + toolbarSeparator - + - + Draggable - + stretch SHOW_RESTORE_MENU - + Draggable HIDEABLE SHOW_RESTORE_MENU - - + + stretch - + Draggable - + Draggable - - + + TrimStack Draggable - + TrimStack Draggable - + TrimStack Draggable - + TrimStack Draggable - - + + TrimStack Draggable - + TrimStack Draggable - + TrimStack Draggable - + TrimStack Draggable - + TrimStack Draggable - - - - - - - - - - - - - - + + + + + + + + + + + + + + platform:gtk - - - - + + + + platform:gtk - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - + + + + + - - + + - - - - - - - - - + + + + + + + + + - - + + - - - + + + - - - - - + + + + + - - + + - - - + + + - - - + + + - - - - - - - - + + + + + + + + platform:gtk - - - - - + + + + + - - + + - - + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - + + + + - - + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - + + - - - - - - - + + + + + + + - - - - + + + + - - - - - - - - + + + + + + + + - - + + - - - - - - + + + + + + - - - - - - + + + + + + - - + + - - - - - - - - + + + + + + + + - - - + + + - - - - + + + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + - - - - - - - - - + + + + + + + + + - - - - - + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Editor removeOnHide - + View categoryTag:Ant - + View categoryTag:Gradle - + View categoryTag:Gradle - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:Java - + View categoryTag:Git - + View categoryTag:Git - + View categoryTag:Git - + View categoryTag:Git NoRestore - + View categoryTag:Git - + View categoryTag:Help - + View categoryTag:Java - + View categoryTag:Java - + View categoryTag:Debug - + View categoryTag:Java - + View categoryTag:Java - + View categoryTag:Java - + View categoryTag:Java Browsing - + View categoryTag:Java Browsing - + View categoryTag:Java Browsing - + View categoryTag:Java Browsing - + View categoryTag:Java - + View categoryTag:General - + View categoryTag:Java - + View categoryTag:Java - + View categoryTag:Docker - + View categoryTag:Docker - + View categoryTag:Docker - + View categoryTag:Docker - + View categoryTag:Language Servers - + View categoryTag:Language Servers - + View categoryTag:Language Servers - + View categoryTag:Maven - + View categoryTag:Maven - + View categoryTag:Maven - + View categoryTag:Mylyn - + View categoryTag:Mylyn - + View categoryTag:Mylyn - + View categoryTag:Mylyn - + View categoryTag:Mylyn - + View categoryTag:Mylyn - + View categoryTag:Oomph - + View categoryTag:Oomph NoRestore - + View categoryTag:Plug-in Development - + View categoryTag:General - + View categoryTag:Version Control (Team) - + View categoryTag:Version Control (Team) - + View categoryTag:Terminal - + View categoryTag:Help - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Help - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Spring - + View categoryTag:Spring - + View categoryTag:Spring - - + + glue move_after:PerspectiveSpacer SHOW_RESTORE_MENU - + move_after:Spacer Glue HIDEABLE SHOW_RESTORE_MENU - + glue move_after:SearchField SHOW_RESTORE_MENU - - - - - + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - + + + - - - - - - - - - + + + + + + + + + - - - - - + + + + + - - - + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.metadata/.plugins/org.eclipse.jdt.ui/OpenTypeHistory.xml b/.metadata/.plugins/org.eclipse.jdt.ui/OpenTypeHistory.xml index 1212e70..386429f 100644 --- a/.metadata/.plugins/org.eclipse.jdt.ui/OpenTypeHistory.xml +++ b/.metadata/.plugins/org.eclipse.jdt.ui/OpenTypeHistory.xml @@ -6,13 +6,15 @@ - + - + + + diff --git a/.metadata/.plugins/org.eclipse.jdt.ui/QualifiedTypeNameHistory.xml b/.metadata/.plugins/org.eclipse.jdt.ui/QualifiedTypeNameHistory.xml index 1b685fd..30b7ca4 100644 --- a/.metadata/.plugins/org.eclipse.jdt.ui/QualifiedTypeNameHistory.xml +++ b/.metadata/.plugins/org.eclipse.jdt.ui/QualifiedTypeNameHistory.xml @@ -34,4 +34,5 @@ + diff --git a/.metadata/.plugins/org.eclipse.m2e.logback/0.log b/.metadata/.plugins/org.eclipse.m2e.logback/0.log index 6380965..ef5fb06 100644 --- a/.metadata/.plugins/org.eclipse.m2e.logback/0.log +++ b/.metadata/.plugins/org.eclipse.m2e.logback/0.log @@ -16,3 +16,6 @@ 2026-03-27 07:46:24,300 [Worker-7: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read. 2026-03-29 16:28:13,219 [Worker-2: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is out-of-date. Trying to update. 2026-03-30 07:33:05,316 [Worker-5: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read. +2026-03-31 08:24:38,073 [Worker-1: 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-31 11:33:17,509 [Worker-8: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read. +2026-03-31 20:02:56,538 [Worker-2: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read. diff --git a/.metadata/version.ini b/.metadata/version.ini index 58386a1..484dbe7 100644 --- a/.metadata/version.ini +++ b/.metadata/version.ini @@ -1,3 +1,3 @@ -#Mon Mar 30 07:33:01 CEST 2026 +#Tue Mar 31 20:02:52 CEST 2026 org.eclipse.core.runtime=2 org.eclipse.platform=4.39.0.v20260226-0420 diff --git a/bilder/Gemini_Generated_Image_xdceypxdceypxdce.png b/bilder/Gemini_Generated_Image_xdceypxdceypxdce.png new file mode 100644 index 0000000..3ebbc3a Binary files /dev/null and b/bilder/Gemini_Generated_Image_xdceypxdceypxdce.png differ diff --git a/bilder/dunno.png b/bilder/dunno.png new file mode 100644 index 0000000..8cdf8fb Binary files /dev/null and b/bilder/dunno.png differ diff --git a/bilder/negative.png b/bilder/negative.png new file mode 100644 index 0000000..7478998 Binary files /dev/null and b/bilder/negative.png differ diff --git a/bilder/neutral.png b/bilder/neutral.png new file mode 100644 index 0000000..64381f2 Binary files /dev/null and b/bilder/neutral.png differ diff --git a/bilder/positiv.png b/bilder/positiv.png new file mode 100644 index 0000000..172c0d8 Binary files /dev/null and b/bilder/positiv.png differ diff --git a/bilder/verynegative.png b/bilder/verynegative.png new file mode 100644 index 0000000..7cfd217 Binary files /dev/null and b/bilder/verynegative.png differ diff --git a/bilder/verypositiv.png b/bilder/verypositiv.png new file mode 100644 index 0000000..8fda4f4 Binary files /dev/null and b/bilder/verypositiv.png differ diff --git a/xxxthegame/deploy.sh b/xxxthegame/deploy.sh new file mode 100755 index 0000000..e7c2406 --- /dev/null +++ b/xxxthegame/deploy.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# Konfiguration +REMOTE_CONTEXT="proxmox-remote" +IMAGE_NAME="xxx-sphere" +TAG="latest" + +echo "--- 1. Gradle Build: Erstelle Docker Image lokal ---" +# Dieser Befehl baut die Jar UND das Docker Image direkt in deinem lokalen Docker +./gradlew bootBuildImage --imageName=$IMAGE_NAME:$TAG + +echo "--- 2. Transfer: Image zum Proxmox-Server schieben ---" +# Wir 'pipen' das Image direkt über SSH auf den Zielserver +docker save $IMAGE_NAME:$TAG | docker --context $REMOTE_CONTEXT load + +echo "--- 3. Remote Deployment: Starten auf Proxmox ---" +# Wir führen Docker Compose direkt im Remote-Kontext aus +# --force-recreate stellt sicher, dass die App mit dem neuen Image neu startet +docker --context $REMOTE_CONTEXT compose up -d --force-recreate + +echo "--- Fertig! Die App läuft auf dem Proxmox-Server ---" diff --git a/xxxthegame/docker-compose.yml b/xxxthegame/docker-compose.yml new file mode 100644 index 0000000..6379ce3 --- /dev/null +++ b/xxxthegame/docker-compose.yml @@ -0,0 +1,32 @@ +services: + db: + image: mysql:8.0 + container_name: mysql-db + restart: always + environment: + MYSQL_DATABASE: xxx_sphere + MYSQL_ROOT_PASSWORD: xxxsphere123! + ports: + - "3306:3306" # <--- Jetzt steht es korrekt alleine! + volumes: + # Format: [Pfad auf dem Proxmox-Host]:[Pfad im Container] + - /mnt/pve_nas/.mysql_data:/var/lib/mysql + + app: + image: xxx-sphere:latest + container_name: spring-boot-app + depends_on: + - db + ports: + - "8080:8080" + environment: + # Wir biegen localhost auf den Service-Namen 'db' um + - SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/xxx_sphere?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC + # Hier injizieren wir die Werte für deine Platzhalter + - DB_USER=root + - DB_PASSWORD=xxxsphere123! + # Wartet kurz, bis die DB wirklich bereit ist (optional, aber empfohlen) + restart: on-failure + +volumes: + mysql_data: diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaGameController.java b/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaGameController.java index d1da618..c247ad7 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaGameController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaGameController.java @@ -238,7 +238,7 @@ public class VanillaGameController { return ResponseEntity.badRequest().build(); } // Max 2 Mitspieler (1 Host + max 1 Gast) - if (session.getMitspieler().size() >= 1) { + if (session.getMitspieler().size() >= 2) { return ResponseEntity.status(409).build(); } VanillaMitspielerEntity entity = new VanillaMitspielerEntity(); diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaToyController.java b/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaToyController.java index b62275a..c76afbe 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaToyController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/vanilla/controller/VanillaToyController.java @@ -4,6 +4,7 @@ import de.oaa.xxx.games.common.aufgaben.Toy; import de.oaa.xxx.games.common.aufgaben.ToyPage; import de.oaa.xxx.games.common.entity.AufgabenGruppeEntity; import de.oaa.xxx.games.common.entity.ToyEntity; +import de.oaa.xxx.games.common.repository.AufgabenGruppeRepository; import de.oaa.xxx.games.common.repository.GruppenAboRepository; import de.oaa.xxx.games.common.repository.ToyRepository; import de.oaa.xxx.subscription.SubscriptionLimitService; @@ -33,6 +34,7 @@ import java.util.Base64; import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; @@ -47,15 +49,18 @@ public class VanillaToyController { private final ToyRepository toyRepository; private final UserService userService; private final GruppenAboRepository aboRepository; + private final AufgabenGruppeRepository gruppeRepository; private final SubscriptionLimitService limitService; public VanillaToyController(ToyRepository toyRepository, UserService userService, GruppenAboRepository aboRepository, + AufgabenGruppeRepository gruppeRepository, SubscriptionLimitService limitService) { this.toyRepository = toyRepository; this.userService = userService; this.aboRepository = aboRepository; + this.gruppeRepository = gruppeRepository; this.limitService = limitService; } @@ -114,6 +119,30 @@ public class VanillaToyController { return ResponseEntity.ok(result); } + /** + * Returns all distinct toys required by aufgaben and finisher of the given gruppe IDs. + * Only vanilla-safe groups (no Strafen, no Sperren) are considered. + */ + @GetMapping("/required") + public ResponseEntity> required(@RequestParam List gruppenIds) { + Map toyMap = new java.util.LinkedHashMap<>(); + gruppeRepository.findAllById(gruppenIds).forEach(gruppe -> { + gruppe.getAufgaben().forEach(a -> { + if (a.getBenoetigteToys() != null) + a.getBenoetigteToys().forEach(t -> toyMap.putIfAbsent(t.getToyId(), t)); + }); + gruppe.getFinisher().forEach(f -> { + if (f.getBenoetigteToys() != null) + f.getBenoetigteToys().forEach(t -> toyMap.putIfAbsent(t.getToyId(), t)); + }); + }); + List result = toyMap.values().stream() + .sorted(Comparator.comparing(ToyEntity::getName, String.CASE_INSENSITIVE_ORDER)) + .map(ToyEntity::toToy) + .toList(); + return ResponseEntity.ok(result); + } + @GetMapping("/{toyId}") public ResponseEntity get(@PathVariable UUID toyId) { return toyRepository.findById(toyId) diff --git a/xxxthegame/src/main/java/de/oaa/xxx/mail/MailService.java b/xxxthegame/src/main/java/de/oaa/xxx/mail/MailService.java index bf61d73..365b63e 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/mail/MailService.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/mail/MailService.java @@ -12,26 +12,28 @@ import org.springframework.stereotype.Service; @Service public class MailService { - private static final Logger LOGGER = LoggerFactory.getLogger(MailService.class); + private static final Logger LOGGER = LoggerFactory.getLogger(MailService.class); - private final JavaMailSender mailSender; + private final JavaMailSender mailSender; - public MailService(JavaMailSender mailSender) { - this.mailSender = mailSender; - } + public MailService(JavaMailSender mailSender) { + this.mailSender = mailSender; + } - public boolean send(Email email) { - try { - MimeMessage message = mailSender.createMimeMessage(); - message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(email.getEmailAdresse())); - message.setSubject(email.getTitel()); - message.setFrom(InternetAddress.parse("noreply@xxx-bdsmgame.de")[0]); - message.setContent(email.getText(), "text/html; charset=utf-8"); - mailSender.send(message); - return true; - } catch (MessagingException e) { - LOGGER.error(e.getLocalizedMessage(), e); - return false; - } - } + public boolean send(Email email) { + try { + MimeMessage message = mailSender.createMimeMessage(); + message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(email.getEmailAdresse())); + message.setSubject(email.getTitel()); + message.setFrom(InternetAddress.parse("noreply@xxx-sphere.de")[0]); + message.setContent(email.getText(), "text/html; charset=utf-8"); + message.addHeader("X-Mailin-Tag", "no-tracking"); + message.addHeader("X-Sib-Attributes", "{\"X-SIB-TRACKING\":\"0\"}"); + mailSender.send(message); + return true; + } catch (MessagingException e) { + LOGGER.error(e.getLocalizedMessage(), e); + return false; + } + } } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/social/SocialController.java b/xxxthegame/src/main/java/de/oaa/xxx/social/SocialController.java index 1d6b12c..0dc97b1 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/social/SocialController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/social/SocialController.java @@ -347,6 +347,7 @@ public class SocialController { user.getSichtbarkeitPinnwand(), user.getSichtbarkeitXp(), user.getSichtbarkeitLockhistorie(), + user.getSichtbarkeitVorlieben(), user.isProfilBeiVeroeffentlichungenSichtbar()); } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/social/dto/UserProfile.java b/xxxthegame/src/main/java/de/oaa/xxx/social/dto/UserProfile.java index efcbc17..a022972 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/social/dto/UserProfile.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/social/dto/UserProfile.java @@ -31,12 +31,13 @@ public record UserProfile( Sichtbarkeit sichtbarkeitPinnwand, Sichtbarkeit sichtbarkeitXp, Sichtbarkeit sichtbarkeitLockhistorie, + Sichtbarkeit sichtbarkeitVorlieben, boolean profilBeiVeroeffentlichungenSichtbar ) { /** Compact constructor for contexts where profile details are not needed (friend list etc.) */ public UserProfile(UUID userId, String name, String profilePicture, String profilePictureHq, String friendStatus) { this(userId, name, profilePicture, profilePictureHq, friendStatus, null, null, null, null, null, null, null, 0, 0, 0, - null, null, null, null, null, null, null, false); + null, null, null, null, null, null, null, null, false); } } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/user/UserController.java b/xxxthegame/src/main/java/de/oaa/xxx/user/UserController.java index fe36def..a8f510f 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/user/UserController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/user/UserController.java @@ -99,6 +99,7 @@ public class UserController { Sichtbarkeit sichtbarkeitPinnwand, Sichtbarkeit sichtbarkeitXp, Sichtbarkeit sichtbarkeitLockhistorie, + Sichtbarkeit sichtbarkeitVorlieben, Boolean profilBeiVeroeffentlichungenSichtbar) {} @PutMapping("/me/picture") @@ -138,6 +139,7 @@ public class UserController { if (request.sichtbarkeitPinnwand() != null) user.setSichtbarkeitPinnwand(request.sichtbarkeitPinnwand()); if (request.sichtbarkeitXp() != null) user.setSichtbarkeitXp(request.sichtbarkeitXp()); if (request.sichtbarkeitLockhistorie()!= null) user.setSichtbarkeitLockhistorie(request.sichtbarkeitLockhistorie()); + if (request.sichtbarkeitVorlieben() != null) user.setSichtbarkeitVorlieben(request.sichtbarkeitVorlieben()); if (request.profilBeiVeroeffentlichungenSichtbar() != null) { boolean showAuthor = request.profilBeiVeroeffentlichungenSichtbar(); user.setProfilBeiVeroeffentlichungenSichtbar(showAuthor); diff --git a/xxxthegame/src/main/java/de/oaa/xxx/user/UserEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/user/UserEntity.java index 7a9f574..bb24437 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/user/UserEntity.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/user/UserEntity.java @@ -92,6 +92,10 @@ public class UserEntity { @Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'") private Sichtbarkeit sichtbarkeitLockhistorie = Sichtbarkeit.ALLE; + @Enumerated(EnumType.STRING) + @Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'") + private Sichtbarkeit sichtbarkeitVorlieben = Sichtbarkeit.ALLE; + @Column(nullable = false, columnDefinition = "TINYINT(1) DEFAULT 0") private boolean profilBeiVeroeffentlichungenSichtbar = false; diff --git a/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/UserVorliebeEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/UserVorliebeEntity.java new file mode 100644 index 0000000..1c57e16 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/UserVorliebeEntity.java @@ -0,0 +1,29 @@ +package de.oaa.xxx.vorlieben; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.util.UUID; + +@Getter +@Setter +@Entity +@Table(name = "user_vorliebe", + uniqueConstraints = @UniqueConstraint(columnNames = {"userId", "itemId"})) +public class UserVorliebeEntity { + + @Id + @Column + private UUID id; + + @Column(nullable = false) + private UUID userId; + + @Column(nullable = false) + private UUID itemId; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 30) + private VorliebeBewertung bewertung; +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/UserVorliebeRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/UserVorliebeRepository.java new file mode 100644 index 0000000..a6bd80d --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/UserVorliebeRepository.java @@ -0,0 +1,12 @@ +package de.oaa.xxx.vorlieben; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface UserVorliebeRepository extends JpaRepository { + List findByUserId(UUID userId); + Optional findByUserIdAndItemId(UUID userId, UUID itemId); +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebeBewertung.java b/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebeBewertung.java new file mode 100644 index 0000000..24a29de --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebeBewertung.java @@ -0,0 +1,16 @@ +package de.oaa.xxx.vorlieben; + +public enum VorliebeBewertung { + GEHT_GAR_NICHT("Geht gar nicht"), + EHER_NICHT("Eher nicht"), + NEUTRAL("Neutral"), + MAG_ICH("Mag ich"), + UNBEDINGT("Unbedingt"), + WILL_AUSPROBIEREN("Will ich ausprobieren"); + + private final String label; + + VorliebeBewertung(String label) { this.label = label; } + + public String getLabel() { return label; } +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebeItemEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebeItemEntity.java new file mode 100644 index 0000000..73cc849 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebeItemEntity.java @@ -0,0 +1,27 @@ +package de.oaa.xxx.vorlieben; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.util.UUID; + +@Getter +@Setter +@Entity +@Table(name = "vorliebe_item") +public class VorliebeItemEntity { + + @Id + @Column + private UUID itemId; + + @Column(nullable = false) + private UUID kategorieId; + + @Column(nullable = false, length = 200) + private String name; + + @Column(nullable = false, columnDefinition = "INT DEFAULT 0") + private int sortOrder; +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebeItemRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebeItemRepository.java new file mode 100644 index 0000000..06886d7 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebeItemRepository.java @@ -0,0 +1,11 @@ +package de.oaa.xxx.vorlieben; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.UUID; + +public interface VorliebeItemRepository extends JpaRepository { + List findAllByOrderBySortOrderAscNameAsc(); + boolean existsByKategorieId(UUID kategorieId); +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebeKategorieEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebeKategorieEntity.java new file mode 100644 index 0000000..fecb547 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebeKategorieEntity.java @@ -0,0 +1,24 @@ +package de.oaa.xxx.vorlieben; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.util.UUID; + +@Getter +@Setter +@Entity +@Table(name = "vorliebe_kategorie") +public class VorliebeKategorieEntity { + + @Id + @Column + private UUID kategorieId; + + @Column(nullable = false, length = 100) + private String name; + + @Column(nullable = false, columnDefinition = "INT DEFAULT 0") + private int sortOrder; +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebeKategorieRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebeKategorieRepository.java new file mode 100644 index 0000000..21de099 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebeKategorieRepository.java @@ -0,0 +1,10 @@ +package de.oaa.xxx.vorlieben; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.UUID; + +public interface VorliebeKategorieRepository extends JpaRepository { + List findAllByOrderBySortOrderAscNameAsc(); +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebenAdminController.java b/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebenAdminController.java new file mode 100644 index 0000000..324c489 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebenAdminController.java @@ -0,0 +1,239 @@ +package de.oaa.xxx.vorlieben; + +import de.oaa.xxx.admin.AdminEntity; +import de.oaa.xxx.admin.AdminRepository; +import de.oaa.xxx.user.UserService; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +import java.security.Principal; +import java.util.*; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/admin/vorlieben") +@Transactional +public class VorliebenAdminController { + + private final VorliebeKategorieRepository kategorieRepository; + private final VorliebeItemRepository itemRepository; + private final UserVorliebeRepository userVorliebeRepository; + private final AdminRepository adminRepository; + private final UserService userService; + + public VorliebenAdminController(VorliebeKategorieRepository kategorieRepository, + VorliebeItemRepository itemRepository, + UserVorliebeRepository userVorliebeRepository, + AdminRepository adminRepository, + UserService userService) { + this.kategorieRepository = kategorieRepository; + this.itemRepository = itemRepository; + this.userVorliebeRepository = userVorliebeRepository; + this.adminRepository = adminRepository; + this.userService = userService; + } + + private AdminEntity requireAdmin(Principal principal) { + var user = userService.requireUser(principal); + return adminRepository.findByUserId(user.getUserId()) + .orElseThrow(() -> new org.springframework.web.server.ResponseStatusException( + org.springframework.http.HttpStatus.FORBIDDEN, "Kein Admin")); + } + + record KategorieRequest(String name, int sortOrder) {} + record ItemRequest(UUID kategorieId, String name, int sortOrder) {} + record KategorieDto(UUID kategorieId, String name, int sortOrder) {} + record ItemDto(UUID itemId, UUID kategorieId, String name, int sortOrder) {} + + // ── Kategorien ──────────────────────────────────────────────────────────── + + @GetMapping("/kategorien") + public ResponseEntity> getKategorien(Principal principal) { + requireAdmin(principal); + List result = kategorieRepository.findAllByOrderBySortOrderAscNameAsc().stream() + .map(k -> new KategorieDto(k.getKategorieId(), k.getName(), k.getSortOrder())) + .toList(); + return ResponseEntity.ok(result); + } + + @PostMapping("/kategorien") + public ResponseEntity createKategorie(@RequestBody KategorieRequest req, Principal principal) { + requireAdmin(principal); + if (req.name() == null || req.name().isBlank()) return ResponseEntity.badRequest().build(); + VorliebeKategorieEntity entity = new VorliebeKategorieEntity(); + entity.setKategorieId(UUID.randomUUID()); + entity.setName(req.name().trim()); + entity.setSortOrder(req.sortOrder()); + kategorieRepository.save(entity); + return ResponseEntity.status(201).body( + new KategorieDto(entity.getKategorieId(), entity.getName(), entity.getSortOrder())); + } + + @PutMapping("/kategorien/{id}") + public ResponseEntity updateKategorie(@PathVariable UUID id, + @RequestBody KategorieRequest req, + Principal principal) { + requireAdmin(principal); + if (req.name() == null || req.name().isBlank()) return ResponseEntity.badRequest().build(); + VorliebeKategorieEntity entity = kategorieRepository.findById(id).orElse(null); + if (entity == null) return ResponseEntity.notFound().build(); + entity.setName(req.name().trim()); + entity.setSortOrder(req.sortOrder()); + kategorieRepository.save(entity); + return ResponseEntity.ok().build(); + } + + @DeleteMapping("/kategorien/{id}") + public ResponseEntity deleteKategorie(@PathVariable UUID id, Principal principal) { + requireAdmin(principal); + if (!kategorieRepository.existsById(id)) return ResponseEntity.notFound().build(); + if (itemRepository.existsByKategorieId(id)) { + return ResponseEntity.status(409) + .header("X-Error", "has-items") + .build(); + } + kategorieRepository.deleteById(id); + return ResponseEntity.ok().build(); + } + + // ── Items ───────────────────────────────────────────────────────────────── + + @GetMapping("/items") + public ResponseEntity> getItems(Principal principal) { + requireAdmin(principal); + List result = itemRepository.findAllByOrderBySortOrderAscNameAsc().stream() + .map(i -> new ItemDto(i.getItemId(), i.getKategorieId(), i.getName(), i.getSortOrder())) + .toList(); + return ResponseEntity.ok(result); + } + + @PostMapping("/items") + public ResponseEntity createItem(@RequestBody ItemRequest req, Principal principal) { + requireAdmin(principal); + if (req.name() == null || req.name().isBlank() || req.kategorieId() == null) + return ResponseEntity.badRequest().build(); + if (!kategorieRepository.existsById(req.kategorieId())) + return ResponseEntity.status(422).header("X-Error", "kategorie-not-found").build(); + VorliebeItemEntity entity = new VorliebeItemEntity(); + entity.setItemId(UUID.randomUUID()); + entity.setKategorieId(req.kategorieId()); + entity.setName(req.name().trim()); + entity.setSortOrder(req.sortOrder()); + itemRepository.save(entity); + return ResponseEntity.status(201).body( + new ItemDto(entity.getItemId(), entity.getKategorieId(), entity.getName(), entity.getSortOrder())); + } + + @PutMapping("/items/{id}") + public ResponseEntity updateItem(@PathVariable UUID id, + @RequestBody ItemRequest req, + Principal principal) { + requireAdmin(principal); + if (req.name() == null || req.name().isBlank()) return ResponseEntity.badRequest().build(); + VorliebeItemEntity entity = itemRepository.findById(id).orElse(null); + if (entity == null) return ResponseEntity.notFound().build(); + if (req.kategorieId() != null && !kategorieRepository.existsById(req.kategorieId())) + return ResponseEntity.status(422).header("X-Error", "kategorie-not-found").build(); + if (req.kategorieId() != null) entity.setKategorieId(req.kategorieId()); + entity.setName(req.name().trim()); + entity.setSortOrder(req.sortOrder()); + itemRepository.save(entity); + return ResponseEntity.ok().build(); + } + + @DeleteMapping("/items/{id}") + public ResponseEntity deleteItem(@PathVariable UUID id, Principal principal) { + requireAdmin(principal); + VorliebeItemEntity entity = itemRepository.findById(id).orElse(null); + if (entity == null) return ResponseEntity.notFound().build(); + // Remove all user ratings for this item first + List ratings = userVorliebeRepository.findAll().stream() + .filter(uv -> uv.getItemId().equals(id)).toList(); + userVorliebeRepository.deleteAll(ratings); + itemRepository.delete(entity); + return ResponseEntity.ok().build(); + } + + // ── Export ──────────────────────────────────────────────────────────────── + + record ExportKategorie(UUID kategorieId, String name, int sortOrder, List items) {} + record ExportItem(UUID itemId, String name, int sortOrder) {} + + @GetMapping("/export") + public ResponseEntity> export(Principal principal) { + requireAdmin(principal); + List kategorien = kategorieRepository.findAllByOrderBySortOrderAscNameAsc(); + List allItems = itemRepository.findAllByOrderBySortOrderAscNameAsc(); + Map> byKat = allItems.stream() + .collect(Collectors.groupingBy(VorliebeItemEntity::getKategorieId)); + + List result = kategorien.stream() + .map(k -> new ExportKategorie( + k.getKategorieId(), k.getName(), k.getSortOrder(), + byKat.getOrDefault(k.getKategorieId(), List.of()).stream() + .map(i -> new ExportItem(i.getItemId(), i.getName(), i.getSortOrder())) + .toList())) + .toList(); + return ResponseEntity.ok() + .header("Content-Disposition", "attachment; filename=\"vorlieben-export.json\"") + .body(result); + } + + // ── Import ──────────────────────────────────────────────────────────────── + + record ImportItem(String name, int sortOrder) {} + record ImportKategorie(String name, int sortOrder, List items) {} + record ImportResult(int kategorienCreated, int kategorienSkipped, int itemsCreated, int itemsSkipped) {} + + @PostMapping("/import") + public ResponseEntity importData(@RequestBody List data, + Principal principal) { + requireAdmin(principal); + if (data == null) return ResponseEntity.badRequest().build(); + + int kCreated = 0, kSkipped = 0, iCreated = 0, iSkipped = 0; + List existingKategorien = kategorieRepository.findAllByOrderBySortOrderAscNameAsc(); + Map byName = existingKategorien.stream() + .collect(Collectors.toMap(k -> k.getName().toLowerCase(), k -> k, + (a, b) -> a)); + + for (ImportKategorie ik : data) { + if (ik.name() == null || ik.name().isBlank()) { kSkipped++; continue; } + VorliebeKategorieEntity kat = byName.get(ik.name().trim().toLowerCase()); + if (kat == null) { + kat = new VorliebeKategorieEntity(); + kat.setKategorieId(UUID.randomUUID()); + kat.setName(ik.name().trim()); + kat.setSortOrder(ik.sortOrder()); + kategorieRepository.save(kat); + byName.put(kat.getName().toLowerCase(), kat); + kCreated++; + } else { + kSkipped++; + } + + if (ik.items() == null) continue; + final UUID katId = kat.getKategorieId(); + List existingItems = + itemRepository.findAllByOrderBySortOrderAscNameAsc().stream() + .filter(i -> i.getKategorieId().equals(katId)) + .toList(); + Set existingNames = existingItems.stream() + .map(i -> i.getName().toLowerCase()).collect(Collectors.toSet()); + + for (ImportItem ii : ik.items()) { + if (ii.name() == null || ii.name().isBlank()) { iSkipped++; continue; } + if (existingNames.contains(ii.name().trim().toLowerCase())) { iSkipped++; continue; } + VorliebeItemEntity item = new VorliebeItemEntity(); + item.setItemId(UUID.randomUUID()); + item.setKategorieId(katId); + item.setName(ii.name().trim()); + item.setSortOrder(ii.sortOrder()); + itemRepository.save(item); + iCreated++; + } + } + return ResponseEntity.ok(new ImportResult(kCreated, kSkipped, iCreated, iSkipped)); + } +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebenController.java b/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebenController.java new file mode 100644 index 0000000..cb1b2ff --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/vorlieben/VorliebenController.java @@ -0,0 +1,148 @@ +package de.oaa.xxx.vorlieben; + +import de.oaa.xxx.social.entity.FriendshipEntity; +import de.oaa.xxx.social.repository.FriendshipRepository; +import de.oaa.xxx.user.Sichtbarkeit; +import de.oaa.xxx.user.UserEntity; +import de.oaa.xxx.user.UserRepository; +import de.oaa.xxx.user.UserService; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +import java.security.Principal; +import java.util.*; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/vorlieben") +@Transactional +public class VorliebenController { + + private final VorliebeKategorieRepository kategorieRepository; + private final VorliebeItemRepository itemRepository; + private final UserVorliebeRepository userVorliebeRepository; + private final UserService userService; + private final UserRepository userRepository; + private final FriendshipRepository friendshipRepository; + + public VorliebenController(VorliebeKategorieRepository kategorieRepository, + VorliebeItemRepository itemRepository, + UserVorliebeRepository userVorliebeRepository, + UserService userService, + UserRepository userRepository, + FriendshipRepository friendshipRepository) { + this.kategorieRepository = kategorieRepository; + this.itemRepository = itemRepository; + this.userVorliebeRepository = userVorliebeRepository; + this.userService = userService; + this.userRepository = userRepository; + this.friendshipRepository = friendshipRepository; + } + + record ItemDto(UUID itemId, String name, int sortOrder) {} + record KategorieWithItems(UUID kategorieId, String name, int sortOrder, List items) {} + + /** Returns all categories with their items – used by the profile edit page. */ + @GetMapping("/items") + public ResponseEntity> getItems() { + List kategorien = kategorieRepository.findAllByOrderBySortOrderAscNameAsc(); + List allItems = itemRepository.findAllByOrderBySortOrderAscNameAsc(); + + Map> byKategorie = allItems.stream() + .collect(Collectors.groupingBy(VorliebeItemEntity::getKategorieId)); + + List result = kategorien.stream() + .map(k -> new KategorieWithItems( + k.getKategorieId(), k.getName(), k.getSortOrder(), + byKategorie.getOrDefault(k.getKategorieId(), List.of()).stream() + .map(i -> new ItemDto(i.getItemId(), i.getName(), i.getSortOrder())) + .toList())) + .filter(k -> !k.items().isEmpty()) + .toList(); + return ResponseEntity.ok(result); + } + + /** Returns the current user's ratings as a map of itemId → bewertung name. */ + @GetMapping("/me") + public ResponseEntity> getMyVorlieben(Principal principal) { + UUID userId = userService.requireUser(principal).getUserId(); + Map result = userVorliebeRepository.findByUserId(userId).stream() + .collect(Collectors.toMap( + uv -> uv.getItemId().toString(), + uv -> uv.getBewertung().name())); + return ResponseEntity.ok(result); + } + + /** Saves the current user's ratings. Value null or blank removes the rating. */ + @PutMapping("/me") + public ResponseEntity saveMyVorlieben(@RequestBody Map ratings, Principal principal) { + UUID userId = userService.requireUser(principal).getUserId(); + for (var entry : ratings.entrySet()) { + UUID itemId; + try { itemId = UUID.fromString(entry.getKey()); } + catch (IllegalArgumentException e) { continue; } + + String bewertungStr = entry.getValue(); + if (bewertungStr == null || bewertungStr.isBlank()) { + userVorliebeRepository.findByUserIdAndItemId(userId, itemId) + .ifPresent(userVorliebeRepository::delete); + } else { + VorliebeBewertung bewertung; + try { bewertung = VorliebeBewertung.valueOf(bewertungStr); } + catch (IllegalArgumentException e) { continue; } + + UserVorliebeEntity uv = userVorliebeRepository.findByUserIdAndItemId(userId, itemId) + .orElseGet(() -> { + UserVorliebeEntity n = new UserVorliebeEntity(); + n.setId(UUID.randomUUID()); + n.setUserId(userId); + n.setItemId(itemId); + return n; + }); + uv.setBewertung(bewertung); + userVorliebeRepository.save(uv); + } + } + return ResponseEntity.ok().build(); + } + + /** Returns another user's ratings, respecting their privacy setting. */ + @GetMapping("/user/{userId}") + public ResponseEntity> getUserVorlieben( + @PathVariable UUID userId, Principal principal) { + UserEntity targetUser = userRepository.findById(userId).orElse(null); + if (targetUser == null) return ResponseEntity.notFound().build(); + + boolean isOwn = false; + boolean isFriend = false; + if (principal != null) { + UUID myId = userService.requireUser(principal).getUserId(); + isOwn = myId.equals(userId); + if (!isOwn) { + Optional f = friendshipRepository.findExisting(myId, userId); + isFriend = f.isPresent() && f.get().getStatus() == FriendshipEntity.Status.ACCEPTED; + } + } + + Sichtbarkeit sv = targetUser.getSichtbarkeitVorlieben(); + if (sv == null) sv = Sichtbarkeit.ALLE; + + boolean canSee = isOwn + || sv == Sichtbarkeit.ALLE + || (sv == Sichtbarkeit.NUR_FREUNDE && isFriend); + + Map result = new LinkedHashMap<>(); + result.put("sichtbarkeit", sv.name()); + result.put("canSee", canSee); + + if (canSee) { + Map ratingsMap = userVorliebeRepository.findByUserId(userId).stream() + .collect(Collectors.toMap( + uv -> uv.getItemId().toString(), + uv -> uv.getBewertung().name())); + result.put("ratings", ratingsMap); + } + return ResponseEntity.ok(result); + } +} diff --git a/xxxthegame/src/main/resources/application.properties b/xxxthegame/src/main/resources/application.properties index e48a358..5432f20 100644 --- a/xxxthegame/src/main/resources/application.properties +++ b/xxxthegame/src/main/resources/application.properties @@ -20,12 +20,12 @@ spring.jpa.properties.hibernate.type.preferred_uuid_jdbc_type=VARCHAR #spring.mail.properties.mail.smtp.starttls.enable=false # Mailpit -spring.mail.host=localhost -spring.mail.port=1025 -spring.mail.username= -spring.mail.password= -spring.mail.properties.mail.smtp.auth=false -spring.mail.properties.mail.smtp.starttls.enable=false +spring.mail.host=smtp-relay.brevo.com +spring.mail.port=587 +spring.mail.username=a6b17a001@smtp-brevo.com +spring.mail.password=xsmtpsib-77b691d562154574133d12b09d44a06e166d30091aac6642480771a0ae463a79-8yH3jHOd4nMMAwuS +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.starttls.enable=true # JWT Keystore jwt.keystore.path=classpath:xxx.jks diff --git a/xxxthegame/src/main/resources/sql/admin.sql b/xxxthegame/src/main/resources/sql/admin.sql new file mode 100644 index 0000000..45675b3 --- /dev/null +++ b/xxxthegame/src/main/resources/sql/admin.sql @@ -0,0 +1,13 @@ +● -- Person zur admin-Tabelle als SUPERADMIN hinzufügen (über E-Mail-Adresse) + INSERT INTO admin (admin_id, user_id, rolle, created_at) + SELECT UUID(), u.user_id, 'SUPERADMIN', NOW() + FROM user u + WHERE u.email = 'email@beispiel.de'; + + -- Falls der User bereits ein (normaler) Admin ist, Rolle upgraden: + UPDATE admin a + JOIN user u ON a.user_id = u.user_id + SET a.rolle = 'SUPERADMIN' + WHERE u.email = 'email@beispiel.de'; + + --Einfach email@beispiel.de durch die Ziel-E-Mail ersetzen. Das erste Statement fügt einen neuen Admin-Eintrag ein, das zweite upgraded einen bestehenden. Nur eines von beiden ausführen je nach Fall. diff --git a/xxxthegame/src/main/resources/static/admin/admin.html b/xxxthegame/src/main/resources/static/admin/admin.html index 9e7addc..9cc1860 100644 --- a/xxxthegame/src/main/resources/static/admin/admin.html +++ b/xxxthegame/src/main/resources/static/admin/admin.html @@ -465,6 +465,7 @@ + @@ -580,6 +581,47 @@ + +
+
+
+

Vorlieben

+
+ + + + +
+
+
+ + + + + +

Wird geladen…

+
+
+
@@ -2337,6 +2379,225 @@ function escAdminHtml(s) { return String(s).replace(/&/g,'&').replace(//g,'>'); } +// ── Vorlieben-Verwaltung ─────────────────────────────────────────────────── + +let _vlKategorien = []; +let _vlItems = []; + +async function loadVorliebenAdmin() { + const [rKat, rItem] = await Promise.all([ + fetch('/admin/vorlieben/kategorien'), + fetch('/admin/vorlieben/items'), + ]); + if (!rKat.ok || !rItem.ok) return; + _vlKategorien = await rKat.json(); + _vlItems = await rItem.json(); + renderVlListe(); + renderVlKatDropdown(); +} + +function renderVlListe() { + const container = document.getElementById('vlKatList'); + if (!_vlKategorien.length) { + container.innerHTML = '

Keine Kategorien vorhanden. Lege zuerst eine Kategorie an.

'; + return; + } + const itemsByKat = {}; + _vlKategorien.forEach(k => { itemsByKat[k.kategorieId] = []; }); + _vlItems.forEach(i => { if (itemsByKat[i.kategorieId]) itemsByKat[i.kategorieId].push(i); }); + + container.innerHTML = _vlKategorien.map(k => { + const items = itemsByKat[k.kategorieId] || []; + const itemsHtml = items.length + ? `
${items.map(i => ` +
+
+ ${escAdminHtml(i.name)} + #${i.sortOrder} +
+ + +
+
+
`).join('')}
` + : `

Keine Vorlieben in dieser Kategorie.

`; + + return ` +
+
+
+
${escAdminHtml(k.name)}
+
${items.length} Vorliebe${items.length !== 1 ? 'n' : ''} · Reihenfolge: ${k.sortOrder}
+
+
+ + + + +
+
+
+
+
+ Vorlieben +
+ ${itemsHtml} +
+
+
`; + }).join(''); +} + +function renderVlKatDropdown() { + const sel = document.getElementById('vlItemKat'); + const cur = sel.value; + sel.innerHTML = _vlKategorien.map(k => + ``).join(''); + if (cur) sel.value = cur; +} + +// ── Kategorien CRUD ── +document.getElementById('vlKatCreateBtn').addEventListener('click', () => { + document.getElementById('vlKatId').value = ''; + document.getElementById('vlKatName').value = ''; + document.getElementById('vlKatSort').value = '0'; + document.getElementById('vlKatFormTitle').textContent = 'Kategorie anlegen'; + document.getElementById('vlItemForm').style.display = 'none'; + document.getElementById('vlKatForm').style.display = ''; + document.getElementById('vlKatName').focus(); +}); + +function cancelKategorie() { document.getElementById('vlKatForm').style.display = 'none'; } + +function editKategorie(id) { + const k = _vlKategorien.find(x => x.kategorieId === id); + if (!k) return; + document.getElementById('vlKatId').value = k.kategorieId; + document.getElementById('vlKatName').value = k.name; + document.getElementById('vlKatSort').value = k.sortOrder; + document.getElementById('vlKatFormTitle').textContent = 'Kategorie bearbeiten'; + document.getElementById('vlItemForm').style.display = 'none'; + document.getElementById('vlKatForm').style.display = ''; + document.getElementById('vlKatName').focus(); +} + +async function saveKategorie() { + const id = document.getElementById('vlKatId').value; + const name = document.getElementById('vlKatName').value.trim(); + const sort = parseInt(document.getElementById('vlKatSort').value) || 0; + const errEl = document.getElementById('vlError'); + if (!name) { errEl.textContent = 'Name darf nicht leer sein.'; return; } + const url = id ? `/admin/vorlieben/kategorien/${id}` : '/admin/vorlieben/kategorien'; + const method = id ? 'PUT' : 'POST'; + const r = await fetch(url, { method, headers: {'Content-Type':'application/json'}, + body: JSON.stringify({ name, sortOrder: sort }) }); + if (r.ok || r.status === 201) { + errEl.textContent = ''; + document.getElementById('vlKatForm').style.display = 'none'; + await loadVorliebenAdmin(); + } else { errEl.textContent = 'Fehler beim Speichern.'; } +} + +async function deleteKategorie(id, name) { + if (!confirm(`Kategorie "${name}" löschen?`)) return; + const errEl = document.getElementById('vlError'); + const r = await fetch(`/admin/vorlieben/kategorien/${id}`, { method: 'DELETE' }); + if (r.ok) { await loadVorliebenAdmin(); } + else if (r.status === 409) { errEl.textContent = 'Kategorie enthält noch Vorlieben – bitte zuerst alle Vorlieben dieser Kategorie löschen.'; } + else { errEl.textContent = 'Fehler beim Löschen.'; } +} + +// ── Items CRUD ── +document.getElementById('vlKatCreateBtn'); // already bound above + +function addItemToKat(katId) { + document.getElementById('vlItemId').value = ''; + document.getElementById('vlItemKat').value = katId; + document.getElementById('vlItemName').value = ''; + document.getElementById('vlItemSort').value = '0'; + document.getElementById('vlItemFormTitle').textContent = 'Vorliebe anlegen'; + document.getElementById('vlKatForm').style.display = 'none'; + document.getElementById('vlItemForm').style.display = ''; + document.getElementById('vlItemName').focus(); +} + +function cancelItem() { document.getElementById('vlItemForm').style.display = 'none'; } + +function editItem(id) { + const i = _vlItems.find(x => x.itemId === id); + if (!i) return; + document.getElementById('vlItemId').value = i.itemId; + document.getElementById('vlItemKat').value = i.kategorieId; + document.getElementById('vlItemName').value = i.name; + document.getElementById('vlItemSort').value = i.sortOrder; + document.getElementById('vlItemFormTitle').textContent = 'Vorliebe bearbeiten'; + document.getElementById('vlKatForm').style.display = 'none'; + document.getElementById('vlItemForm').style.display = ''; + document.getElementById('vlItemName').focus(); +} + +async function saveItem() { + const id = document.getElementById('vlItemId').value; + const katId = document.getElementById('vlItemKat').value; + const name = document.getElementById('vlItemName').value.trim(); + const sort = parseInt(document.getElementById('vlItemSort').value) || 0; + const errEl = document.getElementById('vlError'); + if (!name || !katId) { errEl.textContent = 'Name und Kategorie sind erforderlich.'; return; } + const url = id ? `/admin/vorlieben/items/${id}` : '/admin/vorlieben/items'; + const method = id ? 'PUT' : 'POST'; + const r = await fetch(url, { method, headers: {'Content-Type':'application/json'}, + body: JSON.stringify({ kategorieId: katId, name, sortOrder: sort }) }); + if (r.ok || r.status === 201) { + errEl.textContent = ''; + document.getElementById('vlItemForm').style.display = 'none'; + await loadVorliebenAdmin(); + } else { errEl.textContent = 'Fehler beim Speichern.'; } +} + +async function deleteItem(id, name) { + if (!confirm(`Vorliebe "${name}" löschen? Alle Nutzerbewertungen werden ebenfalls gelöscht.`)) return; + const errEl = document.getElementById('vlError'); + const r = await fetch(`/admin/vorlieben/items/${id}`, { method: 'DELETE' }); + if (r.ok) { await loadVorliebenAdmin(); } + else { errEl.textContent = 'Fehler beim Löschen.'; } +} + +// ── Export / Import ── +document.getElementById('vlExportBtn').addEventListener('click', async () => { + const r = await fetch('/admin/vorlieben/export'); + if (!r.ok) { document.getElementById('vlError').textContent = 'Export fehlgeschlagen.'; return; } + const blob = await r.blob(); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); a.href = url; a.download = 'vorlieben-export.json'; + document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); +}); + +const vlImportFile = document.getElementById('vlImportFile'); +document.getElementById('vlImportBtn').addEventListener('click', () => vlImportFile.click()); +vlImportFile.addEventListener('change', async function () { + if (!this.files.length) return; + const file = this.files[0]; this.value = ''; + const errEl = document.getElementById('vlError'); + errEl.style.color = 'var(--color-muted)'; errEl.textContent = 'Importiere…'; + let data; + try { data = JSON.parse(await file.text()); } + catch(e) { errEl.style.color = ''; errEl.textContent = 'Fehler: Ungültige JSON-Datei.'; return; } + const r = await fetch('/admin/vorlieben/import', { + method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(data) + }); + if (!r.ok) { errEl.style.color = ''; errEl.textContent = 'Import fehlgeschlagen.'; return; } + const res = await r.json(); + await loadVorliebenAdmin(); + errEl.style.color = 'var(--color-success,#2ecc71)'; + errEl.textContent = `Import: ${res.kategorienCreated} Kategorien neu, ${res.itemsCreated} Vorlieben neu, ${res.kategorienSkipped + res.itemsSkipped} übersprungen.`; + setTimeout(() => { errEl.textContent = ''; errEl.style.color = ''; }, 5000); +}); + +// Vorlieben-Tab beim ersten Öffnen laden +document.querySelector('.tab-btn[data-tab="vorlieben"]')?.addEventListener('click', () => { + if (!_vlKategorien.length && !_vlItems.length) loadVorliebenAdmin(); +}); + init(); diff --git a/xxxthegame/src/main/resources/static/community/benutzer.html b/xxxthegame/src/main/resources/static/community/benutzer.html index a9e9a57..004ca70 100644 --- a/xxxthegame/src/main/resources/static/community/benutzer.html +++ b/xxxthegame/src/main/resources/static/community/benutzer.html @@ -347,6 +347,27 @@ .profil-tab-panel { display:none; } .profil-tab-panel.active { display:block; } + /* ── Vorlieben Tab ── */ + .vorlieben-group { margin-bottom:1.25rem; } + .vorlieben-group-title { + font-size:0.78rem; font-weight:700; color:var(--color-muted); + text-transform:uppercase; letter-spacing:0.05em; + margin-bottom:0.5rem; padding-bottom:0.3rem; + border-bottom:1px solid var(--color-secondary); + } + .vorlieben-chips { display:flex; flex-wrap:wrap; gap:0.4rem; } + .vorliebe-chip { + display:inline-block; padding:0.25rem 0.65rem; border-radius:999px; + font-size:0.82rem; border:1px solid var(--color-secondary); + background:var(--color-card); color:var(--color-text); + } + .vorliebe-chip.bw-UNBEDINGT { border-color:#2e7d32; color:#2e7d32; } + .vorliebe-chip.bw-MAG_ICH { border-color:#81c784; color:#81c784; } + .vorliebe-chip.bw-WILL_AUSPROBIEREN{ border-color:#1e88e5; color:#1e88e5; } + .vorliebe-chip.bw-NEUTRAL { border-color:#fdd835; color:#fdd835; } + .vorliebe-chip.bw-EHER_NICHT { border-color:#fb8c00; color:#fb8c00; } + .vorliebe-chip.bw-GEHT_GAR_NICHT { border-color:#e53935; color:#e53935; } + /* ── Post cards (profile posts tab) ── */ .post-card { background:var(--color-card); border:1px solid var(--color-secondary); border-radius:10px; padding:1rem; margin-bottom:0.9rem; } .post-header { display:flex; align-items:center; gap:0.7rem; margin-bottom:0.6rem; } @@ -434,10 +455,11 @@
- +
+
@@ -458,6 +480,11 @@
+ +
+
+
+
@@ -681,17 +708,20 @@ } function applyTabPrivacy(profile, isFriend) { - const showFeed = canSee(profile.sichtbarkeitFeed, isFriend, isOwnProfile); - const showPinnwand = canSee(profile.sichtbarkeitPinnwand, isFriend, isOwnProfile); - const showHistory = canSee(profile.sichtbarkeitLockhistorie, isFriend, isOwnProfile); + const showFeed = canSee(profile.sichtbarkeitFeed, isFriend, isOwnProfile); + const showPinnwand = canSee(profile.sichtbarkeitPinnwand, isFriend, isOwnProfile); + const showHistory = canSee(profile.sichtbarkeitLockhistorie, isFriend, isOwnProfile); + const showVorlieben = canSee(profile.sichtbarkeitVorlieben, isFriend, isOwnProfile); - const btnFeed = document.getElementById('tabBtnPosts'); - const btnPinnwand = document.getElementById('tabBtnPinnwand'); - const btnHistory = document.getElementById('tabBtnGameHistory'); + const btnFeed = document.getElementById('tabBtnPosts'); + const btnPinnwand = document.getElementById('tabBtnPinnwand'); + const btnHistory = document.getElementById('tabBtnGameHistory'); + const btnVorlieben = document.getElementById('tabBtnVorlieben'); if (!showFeed) { btnFeed.style.display = 'none'; document.getElementById('tab-posts').classList.remove('active'); } if (!showPinnwand) { btnPinnwand.style.display = 'none'; } if (!showHistory) { btnHistory.style.display = 'none'; } + if (showVorlieben) { btnVorlieben.style.display = ''; loadVorlieben(); } // Ersten sichtbaren Tab aktivieren if (!showFeed) { @@ -707,6 +737,64 @@ } } + // ── Vorlieben anzeigen ── + const BEWERTUNG_ORDER = ['UNBEDINGT','MAG_ICH','WILL_AUSPROBIEREN','NEUTRAL','EHER_NICHT','GEHT_GAR_NICHT']; + const BEWERTUNG_LABEL = { + UNBEDINGT: 'Unbedingt', MAG_ICH: 'Mag ich', + WILL_AUSPROBIEREN: 'Will ich ausprobieren', NEUTRAL: 'Neutral', + EHER_NICHT: 'Eher nicht', GEHT_GAR_NICHT: 'Geht gar nicht', + }; + let _vorliebenLoaded = false; + + async function loadVorlieben() { + if (_vorliebenLoaded) return; + _vorliebenLoaded = true; + const container = document.getElementById('vorliebenDisplay'); + container.innerHTML = '

Wird geladen…

'; + try { + const [dataRes, itemsRes] = await Promise.all([ + fetch('/vorlieben/user/' + targetUserId), + fetch('/vorlieben/items'), + ]); + if (!dataRes.ok || !itemsRes.ok) { container.innerHTML = '

Fehler beim Laden.

'; return; } + const data = await dataRes.json(); + const kategorien = await itemsRes.json(); + + if (!data.canSee) { + container.innerHTML = '

Nicht sichtbar.

'; return; + } + const ratings = data.ratings || {}; + + // Build itemId → name map + const itemNames = {}; + kategorien.forEach(k => k.items.forEach(i => { itemNames[i.itemId] = i.name; })); + + // Group by bewertung + const grouped = {}; + Object.entries(ratings).forEach(([itemId, bw]) => { + if (!grouped[bw]) grouped[bw] = []; + grouped[bw].push(itemNames[itemId] || itemId); + }); + + const visibleGroups = BEWERTUNG_ORDER.filter(bw => grouped[bw]?.length); + if (!visibleGroups.length) { + container.innerHTML = '

Keine Vorlieben angegeben.

'; return; + } + + container.innerHTML = visibleGroups.map(bw => ` +
+
${BEWERTUNG_LABEL[bw]}
+
+ ${grouped[bw].map(name => + `${escV(name)}`).join('')} +
+
`).join(''); + } catch(e) { + container.innerHTML = '

Fehler beim Laden.

'; + } + } + function escV(s) { return s.replace(/&/g,'&').replace(//g,'>'); } + function showPreviewBanner(mode) { const banner = document.createElement('div'); banner.style.cssText = 'background:var(--color-secondary);border:1px solid var(--color-primary);border-radius:8px;padding:0.65rem 1rem;margin-bottom:1rem;font-size:0.88rem;display:flex;align-items:center;justify-content:space-between;gap:0.75rem;'; diff --git a/xxxthegame/src/main/resources/static/games/vanilla/neuvanilla.html b/xxxthegame/src/main/resources/static/games/vanilla/neuvanilla.html index 452fdf1..d37a768 100644 --- a/xxxthegame/src/main/resources/static/games/vanilla/neuvanilla.html +++ b/xxxthegame/src/main/resources/static/games/vanilla/neuvanilla.html @@ -103,6 +103,7 @@ .aufgaben-section-label { font-size: 0.78rem; font-weight: 600; color: var(--color-muted); text-transform: uppercase; letter-spacing: 0.04em; margin: 1rem 0 0.5rem 0; } .aufgaben-section-label:first-child { margin-top: 0; } .toys-hint { font-size: 0.85rem; color: var(--color-muted); margin-bottom: 1rem; } + .toys-badge { font-size: 0.75rem; font-weight: 600; background: var(--color-primary); color: #fff; border-radius: 999px; padding: 0.1em 0.55em; margin-left: 0.5rem; vertical-align: middle; } /* ── Guest hint ── */ .guest-hint { font-size: 0.8rem; color: var(--color-muted); font-style: italic; margin-bottom: 0.75rem; padding: 0.5rem 0.75rem; background: var(--color-secondary); border-radius: 6px; } @@ -210,7 +211,7 @@
@@ -536,6 +537,8 @@ function onGruppenChanged() { warnungsAkzeptiert = false; hideMessage(); gruppenContent = null; loadedForGruppen = null; toysNeedReload = true; + const badge = document.getElementById('toys-badge'); + badge.style.display = 'none'; badge.textContent = ''; if (document.getElementById('acc-toys-body').classList.contains('is-open')) ladeToys(); } @@ -605,23 +608,35 @@ async function ladeToys() { const toyList = document.getElementById('toyList'); - toyList.innerHTML = '

Lade…

'; - const content = await ladeGruppenContent(); - const toyMap = new Map(); - [...content.aufgaben, ...content.finisher].forEach(item => - (item.benoetigteToys || []).forEach(t => { if (!toyMap.has(t.toyId)) toyMap.set(t.toyId, t); })); - const toys = [...toyMap.values()].sort((a, b) => a.name.localeCompare(b.name)); - if (!toys.length) { - toyList.innerHTML = '

Keine Toys erforderlich – alle Aufgaben können gespielt werden.

'; return; + const selected = getSelectedGruppen(); + if (!selected.length) { + toyList.innerHTML = '

Bitte zuerst Aufgaben-Gruppen auswählen.

'; + toysNeedReload = false; return; + } + toyList.innerHTML = '

Lade…

'; + try { + const params = selected.map(id => `gruppenIds=${encodeURIComponent(id)}`).join('&'); + const res = await fetch(`/vanilla/toy/required?${params}`); + if (!res.ok) throw new Error(); + const toys = await res.json(); + toysNeedReload = false; + const badge = document.getElementById('toys-badge'); + if (toys.length) { badge.textContent = toys.length; badge.style.display = ''; } + else { badge.style.display = 'none'; badge.textContent = ''; } + if (!toys.length) { + toyList.innerHTML = '

Keine Toys erforderlich – alle Aufgaben können gespielt werden.

'; return; + } + toyList.innerHTML = toys.map(toy => { + const checked = savedToyIds === null || savedToyIds.has(toy.toyId); + return ``; + }).join(''); + } catch (_) { + toyList.innerHTML = '

Fehler beim Laden der Toys.

'; } - toyList.innerHTML = toys.map(toy => { - const checked = savedToyIds === null || savedToyIds.has(toy.toyId); - return ``; - }).join(''); } function validateContent(content, mitspieler) { diff --git a/xxxthegame/src/main/resources/static/img/vorlieben/dunno.png b/xxxthegame/src/main/resources/static/img/vorlieben/dunno.png new file mode 100644 index 0000000..8cdf8fb Binary files /dev/null and b/xxxthegame/src/main/resources/static/img/vorlieben/dunno.png differ diff --git a/xxxthegame/src/main/resources/static/img/vorlieben/negative.png b/xxxthegame/src/main/resources/static/img/vorlieben/negative.png new file mode 100644 index 0000000..7478998 Binary files /dev/null and b/xxxthegame/src/main/resources/static/img/vorlieben/negative.png differ diff --git a/xxxthegame/src/main/resources/static/img/vorlieben/neutral.png b/xxxthegame/src/main/resources/static/img/vorlieben/neutral.png new file mode 100644 index 0000000..64381f2 Binary files /dev/null and b/xxxthegame/src/main/resources/static/img/vorlieben/neutral.png differ diff --git a/xxxthegame/src/main/resources/static/img/vorlieben/positiv.png b/xxxthegame/src/main/resources/static/img/vorlieben/positiv.png new file mode 100644 index 0000000..172c0d8 Binary files /dev/null and b/xxxthegame/src/main/resources/static/img/vorlieben/positiv.png differ diff --git a/xxxthegame/src/main/resources/static/img/vorlieben/verynegative.png b/xxxthegame/src/main/resources/static/img/vorlieben/verynegative.png new file mode 100644 index 0000000..7cfd217 Binary files /dev/null and b/xxxthegame/src/main/resources/static/img/vorlieben/verynegative.png differ diff --git a/xxxthegame/src/main/resources/static/img/vorlieben/verypositiv.png b/xxxthegame/src/main/resources/static/img/vorlieben/verypositiv.png new file mode 100644 index 0000000..8fda4f4 Binary files /dev/null and b/xxxthegame/src/main/resources/static/img/vorlieben/verypositiv.png differ diff --git a/xxxthegame/src/main/resources/static/konto/einstellungen.html b/xxxthegame/src/main/resources/static/konto/einstellungen.html index 145035f..d76ce54 100644 --- a/xxxthegame/src/main/resources/static/konto/einstellungen.html +++ b/xxxthegame/src/main/resources/static/konto/einstellungen.html @@ -566,6 +566,19 @@
+ +
+
+
Vorlieben
+
Deine Vorlieben und Bewertungen im Profil
+
+ +
+
@@ -974,6 +987,7 @@ setValue('sv-pinnwand', profile.sichtbarkeitPinnwand || 'ALLE'); setValue('sv-xp', profile.sichtbarkeitXp || 'ALLE'); setValue('sv-lockhistorie', profile.sichtbarkeitLockhistorie || 'ALLE'); + setValue('sv-vorlieben', profile.sichtbarkeitVorlieben || 'ALLE'); setValue('sv-veroeffentlichungen', profile.profilBeiVeroeffentlichungenSichtbar ? 'true' : 'false'); } @@ -991,6 +1005,7 @@ sichtbarkeitPinnwand: document.getElementById('sv-pinnwand').value, sichtbarkeitXp: document.getElementById('sv-xp').value, sichtbarkeitLockhistorie: document.getElementById('sv-lockhistorie').value, + sichtbarkeitVorlieben: document.getElementById('sv-vorlieben').value, profilBeiVeroeffentlichungenSichtbar: document.getElementById('sv-veroeffentlichungen').value === 'true', }; const res = await fetch('/user/me/privacy', { diff --git a/xxxthegame/src/main/resources/static/konto/profile.html b/xxxthegame/src/main/resources/static/konto/profile.html index 87e2509..92369fb 100644 --- a/xxxthegame/src/main/resources/static/konto/profile.html +++ b/xxxthegame/src/main/resources/static/konto/profile.html @@ -95,6 +95,39 @@ margin-bottom: 1rem; } + /* ── Vorlieben Tabs ── */ + .vl-tabs { display: flex; gap: 0; flex-wrap: wrap; + border-bottom: 1px solid var(--color-secondary); margin-bottom: 1rem; } + .vl-tab-btn { background: none; border: none; border-bottom: 3px solid transparent; + border-radius: 0; padding: 0.5rem 1rem; font-size: 0.85rem; font-weight: 600; + color: var(--color-muted); cursor: pointer; margin-bottom: -1px; + transition: color .15s, border-color .15s; white-space: nowrap; } + .vl-tab-btn:hover { color: var(--color-text); background: none; } + .vl-tab-btn.active { color: var(--color-primary); border-bottom-color: var(--color-primary); } + .vl-tab-panel { display: none; } + .vl-tab-panel.active { display: block; } + + /* ── Vorlieben Items ── */ + .vorliebe-row { + display: flex; align-items: center; justify-content: space-between; + gap: 0.75rem; padding: 0.55rem 0.75rem; border-radius: 8px; + background: var(--color-card); border: 1px solid var(--color-secondary); + margin-bottom: 0.45rem; + } + .vorliebe-row-name { font-size: 0.9rem; flex: 1; min-width: 0; } + .vorliebe-smileys { display: flex; gap: 0.25rem; flex-shrink: 0; } + .vl-smiley { + display: inline-flex; align-items: center; cursor: pointer; + border: 2px solid transparent; border-radius: 6px; + padding: 0.1rem 0.15rem; + transition: border-color .15s, transform .1s; + user-select: none; + } + .vl-smiley img { height: 1.6rem; width: auto; display: block; } + .vl-smiley:hover { transform: scale(1.15); } + .vl-smiley.active { border-color: var(--vl-color); } + .vorlieben-hint { font-size: 0.82rem; color: var(--color-muted); margin-bottom: 1rem; } + #ownGallery { display: grid; grid-template-columns: repeat(3, 1fr); @@ -334,6 +367,12 @@ + +

Wähle für jede Vorliebe aus, wie du dazu stehst. Nicht ausgefüllte Einträge werden nicht angezeigt.

+

Wird geladen…

+ + +