diff --git a/.metadata/.lock_info b/.metadata/.lock_info index 6d16bc2..c93b8fc 100644 --- a/.metadata/.lock_info +++ b/.metadata/.lock_info @@ -1,5 +1,5 @@ -#Sun Mar 01 19:35:41 CET 2026 +#Mon Mar 02 07:06:09 CET 2026 display=\:0 host=Mario-Linux -process-id=147181 +process-id=8641 user=mario diff --git a/.metadata/.log b/.metadata/.log index 0914ac8..2a0edce 100644 --- a/.metadata/.log +++ b/.metadata/.log @@ -519,10 +519,33 @@ Command-line arguments: -os linux -ws gtk -arch x86_64 -product org.eclipse.epp !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-01 19:35:42.071 !MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory' +!SESSION 2026-03-02 07:06:00.104 ----------------------------------------------- +eclipse.buildId=4.38.0.20251204-0849 +java.version=21.0.9 +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 org.eclipse.jface 2 0 2026-03-01 19:57:46.028 +!ENTRY ch.qos.logback.classic 1 0 2026-03-02 07:06:04.976 +!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized. + +!ENTRY ch.qos.logback.classic 1 0 2026-03-02 07:06:09.939 +!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-02 07:06:10.126 +!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-02 07:06:10.126 +!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-02 07:06:10.280 +!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-02 07:06:10.280 +!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-02 07:17:32.622 !MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation. -!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-01 19:57:46.028 +!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-02 07:17:32.622 !MESSAGE A conflict occurred for CTRL+SHIFT+T: Binding(CTRL+SHIFT+T, ParameterizedCommand(Command(org.eclipse.jdt.ui.navigate.open.type,Open Type, @@ -541,7 +564,7 @@ Binding(CTRL+SHIFT+T, org.eclipse.ui.defaultAcceleratorConfiguration, org.eclipse.ui.contexts.window,,,system) -!ENTRY org.eclipse.debug.core 4 125 2026-03-01 22:04:05.326 +!ENTRY org.eclipse.debug.core 4 125 2026-03-02 07:17:32.817 !MESSAGE Error logged from Debug Core: !STACK 0 java.io.IOException: Stream closed @@ -553,3 +576,15 @@ java.io.IOException: Stream closed at org.eclipse.debug.internal.core.OutputStreamMonitor.internalRead(OutputStreamMonitor.java:235) at org.eclipse.debug.internal.core.OutputStreamMonitor.read(OutputStreamMonitor.java:211) at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.lsp4e 2 0 2026-03-02 11:04:28.836 +!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) diff --git a/.metadata/.plugins/org.eclipse.buildship.core/project-preferences/xxxthegame b/.metadata/.plugins/org.eclipse.buildship.core/project-preferences/xxxthegame index 3bd2157..f28d608 100644 --- a/.metadata/.plugins/org.eclipse.buildship.core/project-preferences/xxxthegame +++ b/.metadata/.plugins/org.eclipse.buildship.core/project-preferences/xxxthegame @@ -1,5 +1,5 @@ # -#Sun Mar 01 22:07:34 CET 2026 +#Sun Mar 01 19:35:38 CET 2026 buildDir=build buildScriptPath=build.gradle.kts classpath=\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/xxxthegame/.markers b/.metadata/.plugins/org.eclipse.core.resources/.projects/xxxthegame/.markers index 458e800..6502027 100644 Binary files a/.metadata/.plugins/org.eclipse.core.resources/.projects/xxxthegame/.markers and b/.metadata/.plugins/org.eclipse.core.resources/.projects/xxxthegame/.markers differ diff --git a/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources b/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources index b65008c..3d5bec0 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 556977e..f304e75 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 @@ -81,2662 +81,2883 @@ persp.editorOnboardingCommand:Show Key Assist$$$Shift+Ctrl+L persp.editorOnboardingCommand:New$$$Ctrl+N persp.editorOnboardingCommand:Open Type$$$Shift+Ctrl+T - - - + + + org.eclipse.e4.primaryNavigationStack - + View categoryTag:Java - + View categoryTag:Java - + View categoryTag:General - + View categoryTag:Java - - + + View categoryTag:Git - - - - + + + + org.eclipse.e4.secondaryNavigationStack - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Mylyn - + View categoryTag:Java - + View categoryTag:Ant - + org.eclipse.e4.secondaryDataStack Gradle Oomph - active - + View categoryTag:General - + View categoryTag:Java - + View categoryTag:Java - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Terminal - + View categoryTag:Gradle - + View categoryTag:Gradle + + View + categoryTag:Oomph + NoRestore + + + + + + + + persp.actionSet:org.eclipse.mylyn.tasks.ui.navigation + persp.actionSet:org.eclipse.ui.cheatsheets.actionSet + persp.actionSet:org.eclipse.search.searchActionSet + persp.actionSet:org.eclipse.text.quicksearch.actionSet + persp.actionSet:org.eclipse.ui.edit.text.actionSet.annotationNavigation + persp.actionSet:org.eclipse.ui.edit.text.actionSet.navigation + persp.actionSet:org.eclipse.ui.edit.text.actionSet.convertLineDelimitersTo + persp.actionSet:org.eclipse.ui.externaltools.ExternalToolsSet + persp.actionSet:org.eclipse.ui.actionSet.keyBindings + persp.actionSet:org.eclipse.ui.actionSet.openFiles + persp.actionSet:org.springsource.ide.eclipse.commons.launch.actionSet + persp.viewSC:org.eclipse.ui.views.ProgressView + persp.viewSC:org.eclipse.ui.texteditor.TemplatesView + persp.actionSet:org.eclipse.debug.ui.launchActionSet + persp.actionSet:org.eclipse.debug.ui.debugActionSet + persp.actionSet:org.eclipse.ui.NavigateActionSet + persp.viewSC:org.eclipse.debug.ui.DebugView + persp.viewSC:org.eclipse.debug.ui.VariableView + persp.viewSC:org.eclipse.debug.ui.BreakpointView + persp.viewSC:org.eclipse.debug.ui.ExpressionView + persp.viewSC:org.eclipse.ui.views.ContentOutline + persp.viewSC:org.eclipse.ui.console.ConsoleView + persp.viewSC:org.eclipse.ui.views.ProblemView + persp.viewSC:org.eclipse.ui.navigator.ProjectExplorer + persp.viewSC:org.eclipse.pde.runtime.LogView + persp.editorOnboardingImageUri:platform:/plugin/org.eclipse.debug.ui/icons/full/onboarding_debug_persp.svg + persp.editorOnboardingText:Go hunt your bugs here. + persp.actionSet:org.eclipse.debug.ui.breakpointActionSet + persp.showIn:org.eclipse.ui.navigator.ProjectExplorer + persp.editorOnboardingCommand:Find Actions$$$Ctrl+3 + persp.editorOnboardingCommand:Step Into$$$F5 + persp.editorOnboardingCommand:Step Over$$$F6 + persp.editorOnboardingCommand:Step Return$$$F7 + persp.editorOnboardingCommand:Resume$$$F8 + persp.perspSC:org.eclipse.jdt.ui.JavaPerspective + persp.perspSC:org.eclipse.jdt.ui.JavaBrowsingPerspective + persp.actionSet:org.eclipse.jdt.ui.JavaActionSet + persp.showIn:org.eclipse.jdt.ui.PackageExplorer + persp.viewSC:org.eclipse.terminal.view.ui.TerminalsView + persp.showIn:org.eclipse.terminal.view.ui.TerminalsView + persp.actionSet:org.eclipse.jdt.debug.ui.JDTDebugActionSet + persp.viewSC:org.eclipse.jdt.debug.ui.DisplayView + persp.actionSet:org.eclipse.eclemma.ui.CoverageActionSet + persp.showIn:org.eclipse.eclemma.ui.CoverageView + persp.showIn:org.eclipse.egit.ui.RepositoriesView + persp.viewSC:org.eclipse.jdt.junit.ResultView + persp.viewSC:org.eclipse.ant.ui.views.AntView + + + 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 + active + + + View + categoryTag:General + + + View + categoryTag:Debug + + + View + categoryTag:General + + + View + categoryTag:General + + + View + categoryTag:General + + + View + categoryTag:Terminal + + + View + categoryTag:Debug + + + View + categoryTag:General + - - + + View categoryTag:Help - + View categoryTag:General - + View categoryTag:Help - + View categoryTag:Help - + View categoryTag:General - + ViewMenu menuContribution:menu - + - + View categoryTag:Help - - + + EditorStack org.eclipse.e4.primaryDataStack - - - Editor - removeOnHide - org.eclipse.jdt.ui.CompilationUnitEditor - - - - Editor - removeOnHide - org.eclipse.jdt.ui.CompilationUnitEditor - - - - Editor - removeOnHide - org.eclipse.jdt.ui.CompilationUnitEditor - - - - Editor - removeOnHide - org.eclipse.jdt.ui.CompilationUnitEditor - - - - Editor - removeOnHide - org.eclipse.jdt.ui.CompilationUnitEditor - - - + active + noFocus + + Editor removeOnHide org.eclipse.jdt.ui.CompilationUnitEditor + active - + View categoryTag:Java - + ViewMenu menuContribution:menu - + - + View categoryTag:Java - + View categoryTag:General - + - + View categoryTag:General - + ViewMenu menuContribution:menu - + - + View categoryTag:Java - + View categoryTag:Java - + - + View categoryTag:General - + ViewMenu menuContribution:menu - + - + View categoryTag:General - active - activeOnClose - + ViewMenu menuContribution:menu - + - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + ViewMenu menuContribution:menu - + - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Mylyn - + View categoryTag:Terminal - + View categoryTag:Java - + View categoryTag:Git - + View categoryTag:Java - + View categoryTag:Ant - + View categoryTag:Gradle - + ViewMenu menuContribution:menu - + - + View categoryTag:Gradle - + ViewMenu menuContribution:menu - + - - + + + + + View + categoryTag:Oomph + NoRestore + + ViewMenu + menuContribution:menu + + + + + + + + View + categoryTag:Debug + + ViewMenu + menuContribution:menu + + + + + + + View + categoryTag:Debug + + + + + + View + categoryTag:Debug + + ViewMenu + menuContribution:menu + + + + + + + + View + categoryTag:Debug + + ViewMenu + menuContribution:menu + + + + + + + + View + categoryTag:Debug + + ViewMenu + menuContribution:menu + + + + + + + View + categoryTag:General + + + + + View + categoryTag:General + + + + + + View + categoryTag:Debug + + ViewMenu + menuContribution:menu + + + + + toolbarSeparator - + - + Draggable - + - + toolbarSeparator - + - + Draggable - - + + - + toolbarSeparator - + - + Draggable - + Draggable - + Draggable - + Draggable - + toolbarSeparator - + - + Draggable - + - - toolbarSeparator - - - - toolbarSeparator - - - + 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 - - - - - - - - - - - - - + + + + + + + + + + + + + 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: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:Docker - + View categoryTag:Docker - + View categoryTag:Docker - + View categoryTag:Docker - + View categoryTag:Other - + View categoryTag:Other - + View categoryTag:Other - - + + glue move_after:PerspectiveSpacer SHOW_RESTORE_MENU - + move_after:Spacer Glue HIDEABLE SHOW_RESTORE_MENU - + glue move_after:SearchField SHOW_RESTORE_MENU - - - - - + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - + + + - - - - - - - - + + + + + + + + - - - - - + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - + + + + + + + + + - - - - - + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - - - - - - - + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.metadata/.plugins/org.eclipse.jdt.core/1865797976.index b/.metadata/.plugins/org.eclipse.jdt.core/1865797976.index index 4d3bf08..e2feec1 100644 Binary files a/.metadata/.plugins/org.eclipse.jdt.core/1865797976.index and b/.metadata/.plugins/org.eclipse.jdt.core/1865797976.index differ diff --git a/.metadata/.plugins/org.eclipse.jdt.core/9341915.index b/.metadata/.plugins/org.eclipse.jdt.core/9341915.index index 73bb63e..2364e87 100644 Binary files a/.metadata/.plugins/org.eclipse.jdt.core/9341915.index and b/.metadata/.plugins/org.eclipse.jdt.core/9341915.index differ diff --git a/.metadata/.plugins/org.eclipse.jdt.core/externalLibsTimeStamps b/.metadata/.plugins/org.eclipse.jdt.core/externalLibsTimeStamps index 3ebba84..8274170 100644 Binary files a/.metadata/.plugins/org.eclipse.jdt.core/externalLibsTimeStamps and b/.metadata/.plugins/org.eclipse.jdt.core/externalLibsTimeStamps differ diff --git a/.metadata/.plugins/org.eclipse.jdt.core/savedIndexNames.txt b/.metadata/.plugins/org.eclipse.jdt.core/savedIndexNames.txt index d180f8f..35c3593 100644 --- a/.metadata/.plugins/org.eclipse.jdt.core/savedIndexNames.txt +++ b/.metadata/.plugins/org.eclipse.jdt.core/savedIndexNames.txt @@ -1,106 +1,105 @@ INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core -2318770678.index -721517855.index -1914043487.index -836499050.index -1295630681.index -997772539.index -2633787677.index -2769879155.index -3514612140.index -1546736044.index -3515611559.index -970087405.index -2181028596.index -2455962971.index -2070370209.index -2389383899.index -3738696963.index -9341915.index -2978566974.index -1990965588.index -1324521365.index -3912907421.index -1455171009.index -1205982295.index -504781245.index -2237645717.index -225562445.index -1453089870.index -1732769785.index -958756673.index -3372764815.index -3326580390.index -1502997292.index -1502879287.index -1633924572.index -4123041097.index -2519831052.index -2890245412.index -1965154635.index -519552992.index -2874180664.index -3108263030.index -4158338144.index -1653061733.index -2668411497.index -3972616808.index -3602551868.index -2982788279.index -1660713777.index -363836152.index -1256436118.index -2838468603.index -3135354350.index -2891161224.index -2047888269.index -2455882736.index -3758865325.index -2488355463.index -2240786275.index -2191830568.index -2403041570.index -1865797976.index -4195864863.index -1074122571.index -2609856074.index -2236377038.index -198314732.index -1781188320.index -380800336.index -1241285641.index -3662169204.index -3552156823.index -167025465.index -4134502745.index -1118739196.index -2332037983.index -2004806901.index -2503368578.index -2586591901.index -815902026.index -3416862923.index -2655170954.index -1872440599.index 690321491.index -289134298.index -2817101718.index -3547251881.index -808711116.index -3882180612.index -3842019335.index -1446719945.index +1872440599.index +2240786275.index 4150628576.index -2609698604.index -2927822381.index -2725629017.index -3718169413.index -2593736024.index -3154281632.index +2655170954.index +4195864863.index +2982788279.index +2609856074.index 2626965509.index -2398089967.index -1436262503.index -89143789.index -26273648.index -2390245932.index +2769879155.index +4134502745.index +2817101718.index +4158338144.index +519552992.index +2181028596.index +2503368578.index +1453089870.index +2593736024.index +721517855.index +815902026.index +3718169413.index 96642630.index +2488355463.index +1446719945.index +2891161224.index +1118739196.index +2047888269.index +3972616808.index +1205982295.index +1914043487.index +808711116.index +3154281632.index +2390245932.index +2191830568.index +1653061733.index +2586591901.index +2609698604.index +3882180612.index +3758865325.index +2070370209.index +2332037983.index +1732769785.index +2838468603.index +2668411497.index +3662169204.index +2927822381.index +2398089967.index +225562445.index +1436262503.index +1295630681.index +3135354350.index +3602551868.index +363836152.index +504781245.index +2633787677.index +1455171009.index +2725629017.index +3552156823.index +4123041097.index +1865797976.index +3372764815.index +2455962971.index +289134298.index +2389383899.index +26273648.index +3515611559.index +1241285641.index +2978566974.index +958756673.index +3108263030.index +2236377038.index +3547251881.index +2519831052.index +2874180664.index +1781188320.index +970087405.index +1074122571.index +380800336.index +167025465.index +1502997292.index +2237645717.index +3842019335.index +1965154635.index +2403041570.index +2455882736.index +3326580390.index +2004806901.index +836499050.index +3416862923.index +1502879287.index +3912907421.index +89143789.index +1546736044.index +2890245412.index +1990965588.index +3738696963.index +1660713777.index +3514612140.index +997772539.index +198314732.index +1324521365.index +1633924572.index +2318770678.index +1256436118.index diff --git a/.metadata/.plugins/org.eclipse.m2e.logback/0.log b/.metadata/.plugins/org.eclipse.m2e.logback/0.log index 88d1a2a..a5e0966 100644 --- a/.metadata/.plugins/org.eclipse.m2e.logback/0.log +++ b/.metadata/.plugins/org.eclipse.m2e.logback/0.log @@ -1,3 +1,4 @@ 2026-03-01 17:27:17,460 [Worker-2: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is not available. Remote download required. 2026-03-01 18:42:13,387 [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-01 19:35:44,100 [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-02 07:06:13,818 [Worker-6: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read. diff --git a/.metadata/version.ini b/.metadata/version.ini index 69f55ce..2db3de6 100644 --- a/.metadata/version.ini +++ b/.metadata/version.ini @@ -1,3 +1,3 @@ -#Sun Mar 01 19:35:41 CET 2026 +#Mon Mar 02 07:06:09 CET 2026 org.eclipse.core.runtime=2 org.eclipse.platform=4.38.0.v20251201-0920 diff --git a/testdaten/tumblr_o0lh7v8lfw1uu92gho1_1280-1091205047.jpg b/testdaten/tumblr_o0lh7v8lfw1uu92gho1_1280-1091205047.jpg new file mode 100644 index 0000000..0b8d5cc Binary files /dev/null and b/testdaten/tumblr_o0lh7v8lfw1uu92gho1_1280-1091205047.jpg differ diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/AufgabenGruppe.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/AufgabenGruppe.java index 53f7e75..2331da5 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/AufgabenGruppe.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/AufgabenGruppe.java @@ -11,11 +11,12 @@ public class AufgabenGruppe { private String von; private UUID userId; private boolean privateGruppe; - private List toys; private List aufgaben; private List strafen; private List sperren; private String bild; + private long subscriberCount; + private boolean subscribed; public UUID getGruppenId() { return gruppenId; } public void setGruppenId(UUID gruppenId) { this.gruppenId = gruppenId; } @@ -35,9 +36,6 @@ public class AufgabenGruppe { public boolean isPrivateGruppe() { return privateGruppe; } public void setPrivateGruppe(boolean privateGruppe) { this.privateGruppe = privateGruppe; } - public List getToys() { return toys; } - public void setToys(List toys) { this.toys = toys; } - public List getAufgaben() { return aufgaben; } public void setAufgaben(List aufgaben) { this.aufgaben = aufgaben; } @@ -49,4 +47,10 @@ public class AufgabenGruppe { public String getBild() { return bild; } public void setBild(String bild) { this.bild = bild; } + + public long getSubscriberCount() { return subscriberCount; } + public void setSubscriberCount(long subscriberCount) { this.subscriberCount = subscriberCount; } + + public boolean isSubscribed() { return subscribed; } + public void setSubscribed(boolean subscribed) { this.subscribed = subscribed; } } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/AufgabenGruppePage.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/AufgabenGruppePage.java new file mode 100644 index 0000000..f51200b --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/AufgabenGruppePage.java @@ -0,0 +1,23 @@ +package de.oaa.xxx.aufgaben; + +import java.util.List; + +public class AufgabenGruppePage { + + private List content; + private int currentPage; + private int totalPages; + private long totalElements; + + public List getContent() { return content; } + public void setContent(List content) { this.content = content; } + + public int getCurrentPage() { return currentPage; } + public void setCurrentPage(int currentPage) { this.currentPage = currentPage; } + + public int getTotalPages() { return totalPages; } + public void setTotalPages(int totalPages) { this.totalPages = totalPages; } + + public long getTotalElements() { return totalElements; } + public void setTotalElements(long totalElements) { this.totalElements = totalElements; } +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/DefaultFiller.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/DefaultFiller.java index d27cb8c..e64a1a5 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/DefaultFiller.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/DefaultFiller.java @@ -57,10 +57,10 @@ public class DefaultFiller { void chastityFemale() { AufgabenGruppeEntity keuschWiebl = createAufgGruppe("Keuschhaltung weiblich", "Enthält verschiedene Aufgaben für Keuschhaltung von weiblichen Spielpartnern", getClass().getClassLoader().getResourceAsStream("femaleCB.png")); - ToyEntity kg = createToy("KG weiblich", "Ein Voll-Keuschheitsgürtel für die Frau", keuschWiebl); - ToyEntity kgVaginal = createToy("KG weiblich, Vaginaldildo", "Ein Voll-Keuschheitsgürtel für die Frau inkl. eines Vaginaldildos", keuschWiebl); - ToyEntity kgAnal = createToy("KG weiblich, Analdildo", "Ein Voll-Keuschheitsgürtel für die Frau inkl. eines Analdildos", keuschWiebl); - ToyEntity kgDouble = createToy("KG weiblich, Vaginal- u. Analdildo", "Ein Voll-Keuschheitsgürtel für die Frau inkl. eines Vaginal- und Analdildos", keuschWiebl); + ToyEntity kg = createToy("KG weiblich", "Ein Voll-Keuschheitsgürtel für die Frau"); + ToyEntity kgVaginal = createToy("KG weiblich, Vaginaldildo", "Ein Voll-Keuschheitsgürtel für die Frau inkl. eines Vaginaldildos"); + ToyEntity kgAnal = createToy("KG weiblich, Analdildo", "Ein Voll-Keuschheitsgürtel für die Frau inkl. eines Analdildos"); + ToyEntity kgDouble = createToy("KG weiblich, Vaginal- u. Analdildo", "Ein Voll-Keuschheitsgürtel für die Frau inkl. eines Vaginal- und Analdildos"); createSperre("Voll-KG", "{PASSIV} trägt fortan einen Voll-KG, {AKTIV} ist der Keyholder", "{AKTIV}, es ist ab der Zeit {PASSIV} von ihrem KG zu befreien", 10, 30, Arrays.asList(kg), Arrays.asList(VAGINA), keuschWiebl); createSperre("Voll-KG + Vaginaldildo", "{PASSIV} trägt fortan einen Voll-KG mit Vaginaldildo, {AKTIV} ist der Keyholder", "{AKTIV}, es ist ab der Zeit {PASSIV} von ihrem KG zu befreien", 10, 30, Arrays.asList(kgVaginal), Arrays.asList(VAGINA), keuschWiebl); @@ -70,9 +70,9 @@ public class DefaultFiller { void chastityMale() { AufgabenGruppeEntity keuschMaennl = createAufgGruppe("Keuschhaltung männlich", "Enthält verschiedene Aufgaben für Keuschhaltung von männlichen Spielpartnern", getClass().getClassLoader().getResourceAsStream("maleCB.png")); - ToyEntity kaefig = createToy("Peniskäfig", "Ein gewöhnlicher Peniskäfig", keuschMaennl); - ToyEntity kgMaennl = createToy("KG männlich", "Ein Voll-Keuschheitsgürtel für den Mann", keuschMaennl); - ToyEntity knMaennlAnal = createToy("KG männlich, Analdildo", "Ein Voll-Keuschheitsgürtel für den Mann inkl. eines Analdildos oder -plugs", keuschMaennl); + ToyEntity kaefig = createToy("Peniskäfig", "Ein gewöhnlicher Peniskäfig"); + ToyEntity kgMaennl = createToy("KG männlich", "Ein Voll-Keuschheitsgürtel für den Mann"); + ToyEntity knMaennlAnal = createToy("KG männlich, Analdildo", "Ein Voll-Keuschheitsgürtel für den Mann inkl. eines Analdildos oder -plugs"); createSperre("Peniskäfig", "{PASSIV} trägt fortan einen Peniskäfig, {AKTIV} ist der Keyholder", "{AKTIV}, es ist ab der Zeit {PASSIV} von seinem Peniskäfig zu befreien", 10, 30, Arrays.asList(kaefig), Arrays.asList(PENIS), keuschMaennl); createSperre("Voll-KG", "{PASSIV} trägt fortan einen Voll-KG, {AKTIV} ist der Keyholder", "{AKTIV}, es ist ab der Zeit {PASSIV} von seinem KG zu befreien", 10, 30, Arrays.asList(kgMaennl), Arrays.asList(PENIS), keuschMaennl); @@ -81,10 +81,10 @@ public class DefaultFiller { void plugs() { AufgabenGruppeEntity gruppe = createAufgGruppe("Plugs", "Enthält verschiedene Aufgaben für das Tragen von Buttplugs über einen gewissen Zeitraum.", getClass().getClassLoader().getResourceAsStream("plugs.png")); - ToyEntity plugKlein = createToy("Plug klein", "Ein kleiner Buttplug", gruppe); - ToyEntity plugMittel = createToy("Plug mittel", "Ein mittelgroßer Buttplug", gruppe); - ToyEntity plugGross = createToy("Plug groß", "Ein großer Buttplug", gruppe); - ToyEntity plugElektro = createToy("Elektro-Plug", "Ein Elektroplug, der Stromstöße verpasst", gruppe); + ToyEntity plugKlein = createToy("Plug klein", "Ein kleiner Buttplug"); + ToyEntity plugMittel = createToy("Plug mittel", "Ein mittelgroßer Buttplug"); + ToyEntity plugGross = createToy("Plug groß", "Ein großer Buttplug"); + ToyEntity plugElektro = createToy("Elektro-Plug", "Ein Elektroplug, der Stromstöße verpasst"); createSperre("Plug klein", "{AKTIV} führt {PASSIV} einen kleinen Buttplug in anal ein, dieser ist bis auf weiteres zu tragen.", "{AKTIV}, es ist Zeit {PASSIV} von seinem Plug zu befreien", 10, 30, Arrays.asList(plugKlein), Arrays.asList(ANUS), gruppe); createSperre("Plug mittel", "{AKTIV} führt {PASSIV} einen mittelgroßen Buttplug anal ein, dieser ist bis auf weiteres zu tragen.", "{AKTIV}, es ist Zeit {PASSIV} von seinem Plug zu befreien", 10, 30, Arrays.asList(plugMittel), Arrays.asList(ANUS), gruppe); @@ -95,10 +95,10 @@ public class DefaultFiller { void knebel() { AufgabenGruppeEntity gruppe = createAufgGruppe("Knebel", "Enthält verschiedene Aufgaben für das Tragen von Knebeln über einen gewissen Zeitraum.", getClass().getClassLoader().getResourceAsStream("knebel.png")); - ToyEntity ballKnebel = createToy("Ballknebel", "Ein Ballknebel", gruppe); - ToyEntity penisKnebel = createToy("Penisknebel", "Ein Penisknebel", gruppe); - ToyEntity aufblKnebel = createToy("Aufblasbarer Knebel", "Ein aufblasbarer Knebel", gruppe); - ToyEntity isolationsmaske = createToy("Isolationsmaske", "Eine Isolationsmaske", gruppe); + ToyEntity ballKnebel = createToy("Ballknebel", "Ein Ballknebel"); + ToyEntity penisKnebel = createToy("Penisknebel", "Ein Penisknebel"); + ToyEntity aufblKnebel = createToy("Aufblasbarer Knebel", "Ein aufblasbarer Knebel"); + ToyEntity isolationsmaske = createToy("Isolationsmaske", "Eine Isolationsmaske"); createSperre("Ballknebel", "{AKTIV}, lege {PASSIV} einen Ballknebel an, dieser ist bis auf weiteres zu tragen.", "{AKTIV}, es ist Zeit {PASSIV} von seinem Knebel zu befreien.", 10, 30, Arrays.asList(ballKnebel), Arrays.asList(MUND), gruppe); createSperre("Penisknebel", "{AKTIV}, lege {PASSIV} einen Dildoknebel an, dieser ist bis auf weiteres zu tragen.", "{AKTIV}, es ist Zeit {PASSIV} von seinem Knebel zu befreien.", 10, 30, Arrays.asList(penisKnebel), Arrays.asList(MUND), gruppe); @@ -109,22 +109,22 @@ public class DefaultFiller { void stafen() { AufgabenGruppeEntity strafen = createAufgGruppe("Strafen", "Enthält verschiedene Bestrafungen", getClass().getClassLoader().getResourceAsStream("peitsche.png")); - ToyEntity gerte = createToy("Gerte", "Eine gewöhnliche Gerte", strafen); - ToyEntity paddel = createToy("Paddel", "Eine gewöhnliches Paddel", strafen); - ToyEntity peitsche = createToy("Peitsche", "Eine gewöhnliche Peitsche", strafen); - ToyEntity penisKnebel = createToy("Doppel-Penisknebel", "Ein Doppel-Penisknebel", strafen); - ToyEntity handfesseln = createToy("Handfesseln", "Fesseln zum Binden der Hände, z.B. Handschellen", strafen); - ToyEntity plugGross = createToy("Plug groß", "Ein großer Buttplug", strafen); - ToyEntity plugElektro = createToy("Elektro-Plug", "Ein Elektroplug, der Stromstöße verpasst", strafen); - ToyEntity plugPump = createToy("Pump-Plug", "Ein aufblasbarer Plug", strafen); - ToyEntity nippelklemmen = createToy("Nippelklemmen", "Nippelklemmen", strafen); - ToyEntity augenbinde = createToy("Augenbinde", "Eine Augenbinde", strafen); - ToyEntity ballKnebel = createToy("Ballknebel", "Ein Ballknebel", strafen); - ToyEntity strapon = createToy("Strapon", "Ein Umschnalldildo", strafen); - ToyEntity kgMann = createToy("KG Mann", "Ein Voll-KG oder Peniskäfig für den Mann", strafen); - ToyEntity kgFrau = createToy("KG Frau", "Ein Voll-KG die Frau", strafen); - ToyEntity dildoKlein = createToy("Dildo klein", "Ein kleiner Dildo", strafen); - ToyEntity dildoGross = createToy("Dildo groß", "Ein großer Dildo", strafen); + ToyEntity gerte = createToy("Gerte", "Eine gewöhnliche Gerte"); + ToyEntity paddel = createToy("Paddel", "Eine gewöhnliches Paddel"); + ToyEntity peitsche = createToy("Peitsche", "Eine gewöhnliche Peitsche"); + ToyEntity penisKnebel = createToy("Doppel-Penisknebel", "Ein Doppel-Penisknebel"); + ToyEntity handfesseln = createToy("Handfesseln", "Fesseln zum Binden der Hände, z.B. Handschellen"); + ToyEntity plugGross = createToy("Plug groß", "Ein großer Buttplug"); + ToyEntity plugElektro = createToy("Elektro-Plug", "Ein Elektroplug, der Stromstöße verpasst"); + ToyEntity plugPump = createToy("Pump-Plug", "Ein aufblasbarer Plug"); + ToyEntity nippelklemmen = createToy("Nippelklemmen", "Nippelklemmen"); + ToyEntity augenbinde = createToy("Augenbinde", "Eine Augenbinde"); + ToyEntity ballKnebel = createToy("Ballknebel", "Ein Ballknebel"); + ToyEntity strapon = createToy("Strapon", "Ein Umschnalldildo"); + ToyEntity kgMann = createToy("KG Mann", "Ein Voll-KG oder Peniskäfig für den Mann"); + ToyEntity kgFrau = createToy("KG Frau", "Ein Voll-KG die Frau"); + ToyEntity dildoKlein = createToy("Dildo klein", "Ein kleiner Dildo"); + ToyEntity dildoGross = createToy("Dildo groß", "Ein großer Dildo"); createStrafe("5 Schläge mit flachen Hand", "{PASSIV} stellt sich mit dem Gesicht zur Wand, Hände hinterm Kopf, Beine schulterbreit, {AKTIV} verpasst {PASSIV} 5 Schläge mit der flachen Hand auf das Gesäß.", 1, null, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), strafen); @@ -213,9 +213,9 @@ public class DefaultFiller { void aufgaben() { AufgabenGruppeEntity aufgaben = createAufgGruppe("Aufgaben", "Enthält verschiedene Sex-Aufgaben.", getClass().getClassLoader().getResourceAsStream("sex.png")); - ToyEntity vibrator = createToy("Vibrator", "Ein herkömmlicher Vibrator.", aufgaben); - ToyEntity dildoKlein = createToy("Dildo klein", "Ein kleiner Dildo", aufgaben); - ToyEntity dildoGross = createToy("Dildo groß", "Ein großer Dildo", aufgaben); + ToyEntity vibrator = createToy("Vibrator", "Ein herkömmlicher Vibrator."); + ToyEntity dildoKlein = createToy("Dildo klein", "Ein kleiner Dildo"); + ToyEntity dildoGross = createToy("Dildo groß", "Ein großer Dildo"); createAufgabe("Hintern präsentieren", "{AKTIV}, zeig {PASSIV} deinen Hintern, gib dir selber dabei ein oder zwei Klappse auf den Po", 1, null, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), aufgaben); @@ -367,12 +367,11 @@ public class DefaultFiller { return entity; } - private ToyEntity createToy(String name, String beschreibung, AufgabenGruppeEntity gruppe) { + private ToyEntity createToy(String name, String beschreibung) { ToyEntity toy = new ToyEntity(); toy.setToyId(UUID.randomUUID()); toy.setName(name); toy.setBeschreibung(beschreibung); - toy.setAufgabenGruppe(gruppe); toyRepository.save(toy); return toy; } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/ImageScaler.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/ImageScaler.java index ffa56be..0125014 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/ImageScaler.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/ImageScaler.java @@ -3,7 +3,8 @@ package de.oaa.xxx.aufgaben; import org.slf4j.LoggerFactory; import javax.imageio.ImageIO; -import java.awt.Image; +import java.awt.Graphics2D; +import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -11,19 +12,47 @@ import java.io.IOException; public class ImageScaler { - public byte[] scale(byte[] origByte) { - try (ByteArrayInputStream bais = new ByteArrayInputStream(origByte)) { + private static final int MAX_SIZE = 128; + + public byte[] scale(byte[] origBytes) { + try (ByteArrayInputStream bais = new ByteArrayInputStream(origBytes)) { BufferedImage orig = ImageIO.read(bais); - BufferedImage scaled = (BufferedImage) orig.getScaledInstance(128, 128, Image.SCALE_DEFAULT); + if (orig == null) { + return origBytes; + } + + int origWidth = orig.getWidth(); + int origHeight = orig.getHeight(); + + // Bereits klein genug – unverändern zurückgeben + if (origWidth <= MAX_SIZE && origHeight <= MAX_SIZE) { + return origBytes; + } + + // Seitenverhältnis beibehalten: längste Seite auf MAX_SIZE + int newWidth, newHeight; + if (origWidth >= origHeight) { + newWidth = MAX_SIZE; + newHeight = Math.max(1, Math.round((float) MAX_SIZE * origHeight / origWidth)); + } else { + newHeight = MAX_SIZE; + newWidth = Math.max(1, Math.round((float) MAX_SIZE * origWidth / origHeight)); + } + + BufferedImage scaled = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = scaled.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g.drawImage(orig, 0, 0, newWidth, newHeight, null); + g.dispose(); + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { ImageIO.write(scaled, "png", baos); return baos.toByteArray(); - } catch (IOException exception) { - LoggerFactory.getLogger(getClass()).error("Fehler beim Skalieren des Bildes", exception); } - } catch (IOException exception) { - LoggerFactory.getLogger(getClass()).error("Fehler beim Skalieren des Bildes", exception); + } catch (IOException e) { + LoggerFactory.getLogger(ImageScaler.class).error("Fehler beim Skalieren des Bildes", e); + return origBytes; } - return new byte[0]; } } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/Toy.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/Toy.java index a57ca0d..04888e3 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/Toy.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/Toy.java @@ -7,7 +7,8 @@ public class Toy { private UUID toyId; private String name; private String beschreibung; - private UUID gruppeId; + private UUID userId; + private String bild; public UUID getToyId() { return toyId; } public void setToyId(UUID toyId) { this.toyId = toyId; } @@ -18,6 +19,9 @@ public class Toy { public String getBeschreibung() { return beschreibung; } public void setBeschreibung(String beschreibung) { this.beschreibung = beschreibung; } - public UUID getGruppeId() { return gruppeId; } - public void setGruppeId(UUID gruppeId) { this.gruppeId = gruppeId; } + public UUID getUserId() { return userId; } + public void setUserId(UUID userId) { this.userId = userId; } + + public String getBild() { return bild; } + public void setBild(String bild) { this.bild = bild; } } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/ToyList.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/ToyList.java new file mode 100644 index 0000000..429dd6b --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/ToyList.java @@ -0,0 +1,15 @@ +package de.oaa.xxx.aufgaben; + +import java.util.List; + +public class ToyList { + + private List systemToys; + private List userToys; + + public List getSystemToys() { return systemToys; } + public void setSystemToys(List systemToys) { this.systemToys = systemToys; } + + public List getUserToys() { return userToys; } + public void setUserToys(List userToys) { this.userToys = userToys; } +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/ToyPage.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/ToyPage.java new file mode 100644 index 0000000..402cb3e --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/ToyPage.java @@ -0,0 +1,23 @@ +package de.oaa.xxx.aufgaben; + +import java.util.List; + +public class ToyPage { + + private List content; + private int currentPage; + private int totalPages; + private long totalElements; + + public List getContent() { return content; } + public void setContent(List content) { this.content = content; } + + public int getCurrentPage() { return currentPage; } + public void setCurrentPage(int currentPage) { this.currentPage = currentPage; } + + public int getTotalPages() { return totalPages; } + public void setTotalPages(int totalPages) { this.totalPages = totalPages; } + + public long getTotalElements() { return totalElements; } + public void setTotalElements(long totalElements) { this.totalElements = totalElements; } +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/AboController.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/AboController.java new file mode 100644 index 0000000..141c30f --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/AboController.java @@ -0,0 +1,149 @@ +package de.oaa.xxx.aufgaben.controller; + +import de.oaa.xxx.aufgaben.AufgabenGruppe; +import de.oaa.xxx.aufgaben.AufgabenGruppePage; +import de.oaa.xxx.aufgaben.entity.AufgabenGruppeEntity; +import de.oaa.xxx.aufgaben.entity.GruppenAboEntity; +import de.oaa.xxx.aufgaben.repository.AufgabenGruppeRepository; +import de.oaa.xxx.aufgaben.repository.GruppenAboRepository; +import de.oaa.xxx.user.UserEntity; +import de.oaa.xxx.user.UserRepository; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.security.Principal; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping("/abo") +@Transactional +public class AboController { + + private static final int DEFAULT_PAGE_SIZE = 5; + private static final int DISCOVER_PAGE_SIZE = 10; + + private final GruppenAboRepository aboRepository; + private final AufgabenGruppeRepository gruppeRepository; + private final UserRepository userRepository; + + public AboController(GruppenAboRepository aboRepository, + AufgabenGruppeRepository gruppeRepository, + UserRepository userRepository) { + this.aboRepository = aboRepository; + this.gruppeRepository = gruppeRepository; + this.userRepository = userRepository; + } + + // ── Abonnierte Gruppen laden ── + + @GetMapping("/list") + public ResponseEntity listSubscribed( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "" + DEFAULT_PAGE_SIZE) int size, + Principal principal) { + UserEntity user = resolveUser(principal); + if (user == null) return ResponseEntity.status(401).build(); + + List dtos = aboRepository.findByUserId(user.getUserId()).stream() + .map(GruppenAboEntity::getAufgabenGruppe) + .filter(g -> !g.isPrivateGruppe()) // ignoriere inzwischen wieder private Gruppen + .map(g -> enrich(g, user.getUserId(), true)) + .sorted(Comparator.comparing(AufgabenGruppe::getName, String.CASE_INSENSITIVE_ORDER)) + .toList(); + + return ResponseEntity.ok(manualPage(dtos, page, size)); + } + + // ── Entdecken ── + + @GetMapping("/discover") + public ResponseEntity discover( + @RequestParam(required = false) String name, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "" + DISCOVER_PAGE_SIZE) int size, + Principal principal) { + UserEntity user = resolveUser(principal); + if (user == null) return ResponseEntity.status(401).build(); + + String namePattern = name != null && !name.isBlank() ? "%" + name.trim() + "%" : null; + + List dtos = gruppeRepository + .findPublicFromOthers(user.getUserId(), namePattern).stream() + .map(g -> enrich(g, user.getUserId(), aboRepository.existsByUserIdAndAufgabenGruppe(user.getUserId(), g))) + .sorted(Comparator.comparingLong(AufgabenGruppe::getSubscriberCount).reversed() + .thenComparing(AufgabenGruppe::getName, String.CASE_INSENSITIVE_ORDER)) + .toList(); + + return ResponseEntity.ok(manualPage(dtos, page, size)); + } + + // ── Abonnieren ── + + @PostMapping("/{gruppenId}") + public ResponseEntity subscribe(@PathVariable UUID gruppenId, Principal principal) { + UserEntity user = resolveUser(principal); + if (user == null) return ResponseEntity.status(401).build(); + + AufgabenGruppeEntity gruppe = gruppeRepository.findById(gruppenId).orElse(null); + if (gruppe == null || gruppe.isPrivateGruppe() || user.getUserId().equals(gruppe.getUserId())) { + return ResponseEntity.badRequest().build(); + } + if (aboRepository.existsByUserIdAndAufgabenGruppe(user.getUserId(), gruppe)) { + return ResponseEntity.ok().build(); + } + GruppenAboEntity abo = new GruppenAboEntity(); + abo.setAboId(UUID.randomUUID()); + abo.setUserId(user.getUserId()); + abo.setAufgabenGruppe(gruppe); + aboRepository.save(abo); + return ResponseEntity.status(201).build(); + } + + // ── Abonnement kündigen ── + + @DeleteMapping("/{gruppenId}") + public ResponseEntity unsubscribe(@PathVariable UUID gruppenId, Principal principal) { + UserEntity user = resolveUser(principal); + if (user == null) return ResponseEntity.status(401).build(); + + AufgabenGruppeEntity gruppe = gruppeRepository.findById(gruppenId).orElse(null); + if (gruppe == null) return ResponseEntity.noContent().build(); + + aboRepository.deleteByUserIdAndAufgabenGruppe(user.getUserId(), gruppe); + return ResponseEntity.accepted().build(); + } + + // ── Hilfsmethoden ── + + private AufgabenGruppe enrich(AufgabenGruppeEntity entity, UUID userId, boolean subscribed) { + AufgabenGruppe g = entity.toAufgabenGruppe(); + g.setSubscriberCount(aboRepository.countByAufgabenGruppe(entity)); + g.setSubscribed(subscribed); + return g; + } + + private AufgabenGruppePage manualPage(List all, int page, int size) { + int total = all.size(); + int start = page * size; + List content = start >= total ? List.of() : all.subList(start, Math.min(start + size, total)); + AufgabenGruppePage result = new AufgabenGruppePage(); + result.setContent(content); + result.setCurrentPage(page); + result.setTotalPages(total == 0 ? 1 : (int) Math.ceil((double) total / size)); + result.setTotalElements(total); + return result; + } + + private UserEntity resolveUser(Principal principal) { + return userRepository.findByEmail(principal.getName()).orElse(null); + } +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/AufgabeController.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/AufgabeController.java index 993a231..aab7c93 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/AufgabeController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/AufgabeController.java @@ -1,10 +1,13 @@ package de.oaa.xxx.aufgaben.controller; import de.oaa.xxx.aufgaben.Aufgabe; +import de.oaa.xxx.aufgaben.Toy; import de.oaa.xxx.aufgaben.entity.AufgabeEntity; import de.oaa.xxx.aufgaben.entity.AufgabenGruppeEntity; +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.ToyRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; @@ -13,11 +16,14 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; @RestController @@ -29,10 +35,14 @@ public class AufgabeController { private final AufgabeRepository aufgabeRepository; private final AufgabenGruppeRepository gruppeRepository; + private final ToyRepository toyRepository; - public AufgabeController(AufgabeRepository aufgabeRepository, AufgabenGruppeRepository gruppeRepository) { + public AufgabeController(AufgabeRepository aufgabeRepository, + AufgabenGruppeRepository gruppeRepository, + ToyRepository toyRepository) { this.aufgabeRepository = aufgabeRepository; this.gruppeRepository = gruppeRepository; + this.toyRepository = toyRepository; } @GetMapping("/{aufgabeId}") @@ -48,16 +58,39 @@ public class AufgabeController { return ResponseEntity.badRequest().build(); } AufgabenGruppeEntity gruppeEntity = gruppeRepository.findById(aufgabe.getGruppeId()).orElse(null); - if (gruppeEntity == null || gruppeEntity.getAufgaben().size() > 50) { + if (gruppeEntity == null) { return ResponseEntity.badRequest().build(); } - AufgabeEntity entity = AufgabeEntity.create(aufgabe, gruppeEntity); + if (gruppeEntity.getAufgaben().size() >= 100) { + return ResponseEntity.status(409).build(); + } + List toys = resolveToys(aufgabe.getBenoetigteToys()); + AufgabeEntity entity = AufgabeEntity.create(aufgabe, gruppeEntity, toys); aufgabeRepository.save(entity); return ResponseEntity.created( ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(entity.getAufgabeId()).toUri() ).build(); } + @PutMapping("/{aufgabeId}") + public ResponseEntity update(@PathVariable UUID aufgabeId, @RequestBody Aufgabe aufgabe) { + if (aufgabe.getKurzText() == null || aufgabe.getText() == null || aufgabe.getLevel() == null) { + return ResponseEntity.badRequest().build(); + } + AufgabeEntity entity = aufgabeRepository.findById(aufgabeId).orElse(null); + if (entity == null) return ResponseEntity.notFound().build(); + entity.setKurzText(aufgabe.getKurzText()); + entity.setText(aufgabe.getText()); + entity.setLevel(aufgabe.getLevel()); + entity.setSekundenVon(aufgabe.getSekundenVon()); + entity.setSekundenBis(aufgabe.getSekundenBis()); + entity.setBenoetigtAktiv(aufgabe.getBenoetigtAktiv()); + entity.setBenoetigtPassiv(aufgabe.getBenoetigtPassiv()); + entity.setBenoetigteToys(resolveToys(aufgabe.getBenoetigteToys())); + aufgabeRepository.save(entity); + return ResponseEntity.ok().build(); + } + @DeleteMapping public ResponseEntity delete(@RequestBody Aufgabe aufgabe) { try { @@ -68,4 +101,14 @@ public class AufgabeController { return ResponseEntity.internalServerError().build(); } } + + private List resolveToys(List toys) { + if (toys == null || toys.isEmpty()) return new ArrayList<>(); + List ids = toys.stream() + .filter(t -> t.getToyId() != null) + .map(Toy::getToyId) + .toList(); + if (ids.isEmpty()) return new ArrayList<>(); + return toyRepository.findAllById(ids); + } } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/AufgabenGruppeController.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/AufgabenGruppeController.java index ca1fa57..b139f4f 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/AufgabenGruppeController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/AufgabenGruppeController.java @@ -2,11 +2,25 @@ package de.oaa.xxx.aufgaben.controller; import de.oaa.xxx.aufgaben.AufgabenGruppe; import de.oaa.xxx.aufgaben.AufgabenGruppeList; +import de.oaa.xxx.aufgaben.AufgabenGruppePage; +import de.oaa.xxx.aufgaben.entity.AufgabeEntity; import de.oaa.xxx.aufgaben.entity.AufgabenGruppeEntity; +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.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.LoggerFactory; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.http.ResponseEntity; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.transaction.annotation.Transactional; @@ -14,12 +28,21 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; 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.servlet.support.ServletUriComponentsBuilder; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.UUID; @RestController @@ -28,13 +51,57 @@ import java.util.UUID; public class AufgabenGruppeController { private static final Logger LOGGER = LoggerFactory.getLogger(AufgabenGruppeController.class); + private static final int DEFAULT_PAGE_SIZE = 5; private final AufgabenGruppeRepository gruppeRepository; + private final AufgabeRepository aufgabeRepository; + private final StrafeRepository strafeRepository; + private final SperreRepository sperreRepository; + private final UserRepository userRepository; + private final GruppenAboRepository aboRepository; + private final ToyRepository toyRepository; - public AufgabenGruppeController(AufgabenGruppeRepository gruppeRepository) { + public AufgabenGruppeController(AufgabenGruppeRepository gruppeRepository, + AufgabeRepository aufgabeRepository, + StrafeRepository strafeRepository, + SperreRepository sperreRepository, + UserRepository userRepository, + GruppenAboRepository aboRepository, + ToyRepository toyRepository) { this.gruppeRepository = gruppeRepository; + this.aufgabeRepository = aufgabeRepository; + this.strafeRepository = strafeRepository; + this.sperreRepository = sperreRepository; + this.userRepository = userRepository; + this.aboRepository = aboRepository; + this.toyRepository = toyRepository; } + // ── Paginierte Listen ── + + @GetMapping("/list/user") + public ResponseEntity listUser( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "" + DEFAULT_PAGE_SIZE) int size, + Principal principal) { + UserEntity user = resolveUser(principal); + if (user == null) return ResponseEntity.status(401).build(); + Page result = gruppeRepository.findByUserId( + user.getUserId(), PageRequest.of(page, size, Sort.by("name"))); + return ResponseEntity.ok(toGruppePage(result, true)); + } + + @GetMapping("/list/system") + public ResponseEntity listSystem( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "" + DEFAULT_PAGE_SIZE) int size) { + Page result = gruppeRepository.findByUserIdIsNull( + PageRequest.of(page, size, Sort.by("name"))); + return ResponseEntity.ok(toGruppePage(result)); + } + + // ── Bestehende Endpunkte ── + @GetMapping("/all") public ResponseEntity getAll(@RequestParam(required = false) String search) { UUID userId = (UUID) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); @@ -60,15 +127,185 @@ public class AufgabenGruppeController { .orElse(ResponseEntity.noContent().build()); } + // ── Anlegen ── + @PostMapping - public ResponseEntity create(@RequestBody AufgabenGruppe gruppe) { + public ResponseEntity create(@RequestBody AufgabenGruppe gruppe, Principal principal) { + if (gruppe.getName() == null || gruppe.getName().isBlank()) { + return ResponseEntity.badRequest().build(); + } + UserEntity user = resolveUser(principal); + if (user == null) return ResponseEntity.status(401).build(); + + if (gruppeRepository.countByUserId(user.getUserId()) >= 10) { + return ResponseEntity.status(409).build(); + } + AufgabenGruppeEntity entity = AufgabenGruppeEntity.create(gruppe); + entity.setUserId(user.getUserId()); + entity.setPrivateGruppe(true); gruppeRepository.save(entity); return ResponseEntity.created( ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(entity.getGruppenId()).toUri() ).build(); } + // ── Bearbeiten ── + + @PutMapping("/{gruppeId}") + public ResponseEntity update(@PathVariable UUID gruppeId, + @RequestBody AufgabenGruppe gruppe, + Principal principal) { + if (gruppe.getName() == null || gruppe.getName().isBlank()) { + return ResponseEntity.badRequest().build(); + } + UserEntity user = resolveUser(principal); + if (user == null) return ResponseEntity.status(401).build(); + + AufgabenGruppeEntity entity = gruppeRepository.findById(gruppeId).orElse(null); + if (entity == null) return ResponseEntity.notFound().build(); + if (!user.getUserId().equals(entity.getUserId())) return ResponseEntity.status(403).build(); + + entity.setName(gruppe.getName().trim()); + entity.setBeschreibung(gruppe.getBeschreibung()); + entity.setVon(gruppe.getVon()); + entity.setPrivateGruppe(gruppe.isPrivateGruppe()); + if (gruppe.getBild() != null) { + entity.setBild(Base64.getDecoder().decode(gruppe.getBild())); + } + gruppeRepository.save(entity); + return ResponseEntity.ok().build(); + } + + // ── Kopieren (Systemgruppe → eigene) ── + + @PostMapping("/copy/{gruppeId}") + public ResponseEntity copy(@PathVariable UUID gruppeId, Principal principal) { + UserEntity user = resolveUser(principal); + if (user == null) return ResponseEntity.status(401).build(); + + if (gruppeRepository.countByUserId(user.getUserId()) >= 10) { + return ResponseEntity.status(409).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 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()); }); + + Map 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); + } + + return ResponseEntity.status(201).build(); + } + + private List mapToys(List source, Map mapping) { + if (source == null || source.isEmpty()) return new ArrayList<>(); + return source.stream().map(t -> mapping.getOrDefault(t.getToyId(), t)).toList(); + } + + // ── Löschen ── + + @DeleteMapping("/{gruppeId}") + public ResponseEntity deleteById(@PathVariable UUID gruppeId, Principal principal) { + UserEntity user = resolveUser(principal); + if (user == null) return ResponseEntity.status(401).build(); + + AufgabenGruppeEntity entity = gruppeRepository.findById(gruppeId).orElse(null); + if (entity == null) return ResponseEntity.noContent().build(); + if (!user.getUserId().equals(entity.getUserId())) return ResponseEntity.status(403).build(); + + try { + aboRepository.deleteByAufgabenGruppe(entity); + aufgabeRepository.deleteAll(entity.getAufgaben()); + strafeRepository.deleteAll(entity.getStrafen()); + sperreRepository.deleteAll(entity.getSperren()); + gruppeRepository.delete(entity); + return ResponseEntity.accepted().build(); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + return ResponseEntity.internalServerError().build(); + } + } + @DeleteMapping public ResponseEntity delete(@RequestBody AufgabenGruppe gruppe) { try { @@ -79,4 +316,27 @@ public class AufgabenGruppeController { return ResponseEntity.internalServerError().build(); } } + + // ── Hilfsmethoden ── + + private UserEntity resolveUser(Principal principal) { + return userRepository.findByEmail(principal.getName()).orElse(null); + } + + private AufgabenGruppePage toGruppePage(Page page) { + return toGruppePage(page, false); + } + + private AufgabenGruppePage toGruppePage(Page page, boolean withSubscriberCount) { + AufgabenGruppePage result = new AufgabenGruppePage(); + result.setContent(page.getContent().stream().map(entity -> { + AufgabenGruppe g = entity.toAufgabenGruppe(); + if (withSubscriberCount) g.setSubscriberCount(aboRepository.countByAufgabenGruppe(entity)); + return g; + }).toList()); + result.setCurrentPage(page.getNumber()); + result.setTotalPages(page.getTotalPages()); + result.setTotalElements(page.getTotalElements()); + return result; + } } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/SperreController.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/SperreController.java index e5102b8..d44e2aa 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/SperreController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/SperreController.java @@ -1,10 +1,13 @@ package de.oaa.xxx.aufgaben.controller; import de.oaa.xxx.aufgaben.Sperre; +import de.oaa.xxx.aufgaben.Toy; import de.oaa.xxx.aufgaben.entity.AufgabenGruppeEntity; import de.oaa.xxx.aufgaben.entity.SperreEntity; +import de.oaa.xxx.aufgaben.entity.ToyEntity; import de.oaa.xxx.aufgaben.repository.AufgabenGruppeRepository; import de.oaa.xxx.aufgaben.repository.SperreRepository; +import de.oaa.xxx.aufgaben.repository.ToyRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; @@ -13,11 +16,14 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; @RestController("aufgabenSperreController") @@ -29,10 +35,14 @@ public class SperreController { private final SperreRepository sperreRepository; private final AufgabenGruppeRepository gruppeRepository; + private final ToyRepository toyRepository; - public SperreController(SperreRepository sperreRepository, AufgabenGruppeRepository gruppeRepository) { + public SperreController(SperreRepository sperreRepository, + AufgabenGruppeRepository gruppeRepository, + ToyRepository toyRepository) { this.sperreRepository = sperreRepository; this.gruppeRepository = gruppeRepository; + this.toyRepository = toyRepository; } @GetMapping("/{sperreId}") @@ -49,16 +59,39 @@ public class SperreController { return ResponseEntity.badRequest().build(); } AufgabenGruppeEntity gruppeEntity = gruppeRepository.findById(sperre.getGruppeId()).orElse(null); - if (gruppeEntity == null || gruppeEntity.getAufgaben().size() > 50) { + if (gruppeEntity == null) { return ResponseEntity.badRequest().build(); } - SperreEntity entity = SperreEntity.create(sperre, gruppeEntity); + if (gruppeEntity.getSperren().size() >= 100) { + return ResponseEntity.status(409).build(); + } + List toys = resolveToys(sperre.getBenoetigteToys()); + SperreEntity entity = SperreEntity.create(sperre, gruppeEntity, toys); sperreRepository.save(entity); return ResponseEntity.created( ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(entity.getSperreId()).toUri() ).build(); } + @PutMapping("/{sperreId}") + public ResponseEntity update(@PathVariable UUID sperreId, @RequestBody Sperre sperre) { + if (sperre.getKurzText() == null || sperre.getText() == null || sperre.getMinutenVon() == null + || sperre.getSperreFuer() == null || sperre.getSperreFuer().isEmpty()) { + return ResponseEntity.badRequest().build(); + } + SperreEntity entity = sperreRepository.findById(sperreId).orElse(null); + if (entity == null) return ResponseEntity.notFound().build(); + entity.setKurzText(sperre.getKurzText()); + entity.setText(sperre.getText()); + entity.setReleaseText(sperre.getReleaseText()); + entity.setMinutenVon(sperre.getMinutenVon()); + entity.setMinutenBis(sperre.getMinutenBis()); + entity.setSperreFuer(sperre.getSperreFuer()); + entity.setBenoetigteToys(resolveToys(sperre.getBenoetigteToys())); + sperreRepository.save(entity); + return ResponseEntity.ok().build(); + } + @DeleteMapping public ResponseEntity delete(@RequestBody Sperre sperre) { try { @@ -69,4 +102,14 @@ public class SperreController { return ResponseEntity.internalServerError().build(); } } + + private List resolveToys(List toys) { + if (toys == null || toys.isEmpty()) return new ArrayList<>(); + List ids = toys.stream() + .filter(t -> t.getToyId() != null) + .map(Toy::getToyId) + .toList(); + if (ids.isEmpty()) return new ArrayList<>(); + return toyRepository.findAllById(ids); + } } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/StrafeController.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/StrafeController.java index 4e10c1d..bef7d2b 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/StrafeController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/StrafeController.java @@ -1,10 +1,13 @@ package de.oaa.xxx.aufgaben.controller; import de.oaa.xxx.aufgaben.Strafe; +import de.oaa.xxx.aufgaben.Toy; import de.oaa.xxx.aufgaben.entity.AufgabenGruppeEntity; import de.oaa.xxx.aufgaben.entity.StrafeEntity; +import de.oaa.xxx.aufgaben.entity.ToyEntity; import de.oaa.xxx.aufgaben.repository.AufgabenGruppeRepository; 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.http.ResponseEntity; @@ -13,11 +16,14 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; @RestController @@ -29,10 +35,14 @@ public class StrafeController { private final StrafeRepository strafeRepository; private final AufgabenGruppeRepository gruppeRepository; + private final ToyRepository toyRepository; - public StrafeController(StrafeRepository strafeRepository, AufgabenGruppeRepository gruppeRepository) { + public StrafeController(StrafeRepository strafeRepository, + AufgabenGruppeRepository gruppeRepository, + ToyRepository toyRepository) { this.strafeRepository = strafeRepository; this.gruppeRepository = gruppeRepository; + this.toyRepository = toyRepository; } @GetMapping("/{strafeId}") @@ -48,16 +58,39 @@ public class StrafeController { return ResponseEntity.badRequest().build(); } AufgabenGruppeEntity gruppeEntity = gruppeRepository.findById(strafe.getGruppeId()).orElse(null); - if (gruppeEntity == null || gruppeEntity.getAufgaben().size() > 50) { + if (gruppeEntity == null) { return ResponseEntity.badRequest().build(); } - StrafeEntity entity = StrafeEntity.create(strafe, gruppeEntity); + if (gruppeEntity.getStrafen().size() >= 100) { + return ResponseEntity.status(409).build(); + } + List toys = resolveToys(strafe.getBenoetigteToys()); + StrafeEntity entity = StrafeEntity.create(strafe, gruppeEntity, toys); strafeRepository.save(entity); return ResponseEntity.created( ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(entity.getStrafeId()).toUri() ).build(); } + @PutMapping("/{strafeId}") + public ResponseEntity update(@PathVariable UUID strafeId, @RequestBody Strafe strafe) { + if (strafe.getKurzText() == null || strafe.getText() == null || strafe.getLevel() == null) { + return ResponseEntity.badRequest().build(); + } + StrafeEntity entity = strafeRepository.findById(strafeId).orElse(null); + if (entity == null) return ResponseEntity.notFound().build(); + entity.setKurzText(strafe.getKurzText()); + entity.setText(strafe.getText()); + entity.setLevel(strafe.getLevel()); + entity.setSekundenVon(strafe.getSekundenVon()); + entity.setSekundenBis(strafe.getSekundenBis()); + entity.setBenoetigtAktiv(strafe.getBenoetigtAktiv()); + entity.setBenoetigtPassiv(strafe.getBenoetigtPassiv()); + entity.setBenoetigteToys(resolveToys(strafe.getBenoetigteToys())); + strafeRepository.save(entity); + return ResponseEntity.ok().build(); + } + @DeleteMapping public ResponseEntity delete(@RequestBody Strafe strafe) { try { @@ -68,4 +101,14 @@ public class StrafeController { return ResponseEntity.internalServerError().build(); } } + + private List resolveToys(List toys) { + if (toys == null || toys.isEmpty()) return new ArrayList<>(); + List ids = toys.stream() + .filter(t -> t.getToyId() != null) + .map(Toy::getToyId) + .toList(); + if (ids.isEmpty()) return new ArrayList<>(); + return toyRepository.findAllById(ids); + } } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/ToyController.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/ToyController.java index 71e6b47..1c85c5f 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/ToyController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/controller/ToyController.java @@ -1,23 +1,38 @@ package de.oaa.xxx.aufgaben.controller; import de.oaa.xxx.aufgaben.Toy; +import de.oaa.xxx.aufgaben.ToyPage; import de.oaa.xxx.aufgaben.entity.AufgabenGruppeEntity; import de.oaa.xxx.aufgaben.entity.ToyEntity; -import de.oaa.xxx.aufgaben.repository.AufgabenGruppeRepository; +import de.oaa.xxx.aufgaben.repository.GruppenAboRepository; 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.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; 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.servlet.support.ServletUriComponentsBuilder; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.UUID; @RestController @@ -26,13 +41,83 @@ import java.util.UUID; public class ToyController { private static final Logger LOGGER = LoggerFactory.getLogger(ToyController.class); + private static final int DEFAULT_PAGE_SIZE = 12; private final ToyRepository toyRepository; - private final AufgabenGruppeRepository gruppeRepository; + private final UserRepository userRepository; + private final GruppenAboRepository aboRepository; - public ToyController(ToyRepository toyRepository, AufgabenGruppeRepository gruppeRepository) { + public ToyController(ToyRepository toyRepository, + UserRepository userRepository, + GruppenAboRepository aboRepository) { this.toyRepository = toyRepository; - this.gruppeRepository = gruppeRepository; + this.userRepository = userRepository; + this.aboRepository = aboRepository; + } + + @GetMapping("/list/user") + public ResponseEntity listUser( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "" + DEFAULT_PAGE_SIZE) int size, + Principal principal) { + UserEntity user = userRepository.findByEmail(principal.getName()).orElse(null); + if (user == null) { + return ResponseEntity.status(401).build(); + } + Page result = toyRepository.findByUserId( + user.getUserId(), PageRequest.of(page, size, Sort.by("name"))); + return ResponseEntity.ok(toToyPage(result)); + } + + @GetMapping("/list/system") + public ResponseEntity listSystem( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "" + DEFAULT_PAGE_SIZE) int size) { + Page result = toyRepository.findByUserIdIsNull( + PageRequest.of(page, size, Sort.by("name"))); + return ResponseEntity.ok(toToyPage(result)); + } + + /** + * Returns all toys available to the current user for assignment to items: + * own toys + system toys + toys referenced in subscribed groups' items. + */ + @GetMapping("/available") + public ResponseEntity> available(Principal principal) { + UserEntity user = userRepository.findByEmail(principal.getName()).orElse(null); + if (user == null) return ResponseEntity.status(401).build(); + + List own = toyRepository.findByUserId(user.getUserId(), PageRequest.of(0, 500, Sort.by("name"))).getContent(); + List system = toyRepository.findByUserIdIsNull(PageRequest.of(0, 500, Sort.by("name"))).getContent(); + + Set knownIds = new HashSet<>(); + own.forEach(t -> knownIds.add(t.getToyId())); + system.forEach(t -> knownIds.add(t.getToyId())); + + Set fromAbos = new HashSet<>(); + aboRepository.findByUserId(user.getUserId()).forEach(abo -> { + AufgabenGruppeEntity gruppe = abo.getAufgabenGruppe(); + gruppe.getAufgaben().forEach(a -> { + if (a.getBenoetigteToys() != null) + a.getBenoetigteToys().stream().filter(t -> !knownIds.contains(t.getToyId())).forEach(fromAbos::add); + }); + gruppe.getStrafen().forEach(s -> { + if (s.getBenoetigteToys() != null) + s.getBenoetigteToys().stream().filter(t -> !knownIds.contains(t.getToyId())).forEach(fromAbos::add); + }); + gruppe.getSperren().forEach(sp -> { + if (sp.getBenoetigteToys() != null) + sp.getBenoetigteToys().stream().filter(t -> !knownIds.contains(t.getToyId())).forEach(fromAbos::add); + }); + }); + + List result = new ArrayList<>(); + result.addAll(own.stream().map(ToyEntity::toToy).toList()); + result.addAll(system.stream().map(ToyEntity::toToy).toList()); + result.addAll(fromAbos.stream() + .sorted(Comparator.comparing(ToyEntity::getName, String.CASE_INSENSITIVE_ORDER)) + .map(ToyEntity::toToy).toList()); + return ResponseEntity.ok(result); } @GetMapping("/{toyId}") @@ -43,31 +128,120 @@ public class ToyController { } @PostMapping - public ResponseEntity create(@RequestBody Toy toy) { - if (toy.getName() == null || toy.getGruppeId() == null) { + public ResponseEntity create(@RequestBody Toy toy, Principal principal) { + if (toy.getName() == null || toy.getName().isBlank()) { return ResponseEntity.badRequest().build(); } - AufgabenGruppeEntity gruppeEntity = gruppeRepository.findById(toy.getGruppeId()).orElse(null); - if (gruppeEntity == null || gruppeEntity.getAufgaben().size() > 50) { - return ResponseEntity.badRequest().build(); + UserEntity user = userRepository.findByEmail(principal.getName()).orElse(null); + if (user == null) { + return ResponseEntity.status(401).build(); } - ToyEntity entity = ToyEntity.create(toy, gruppeEntity); + if (toyRepository.existsByNameIgnoreCaseAndUserIdIsNull(toy.getName()) + || toyRepository.existsByNameIgnoreCaseAndUserId(toy.getName(), user.getUserId())) { + return ResponseEntity.status(409) + .header("X-Error", "duplicate-name") + .build(); + } + ToyEntity entity = ToyEntity.create(toy); + entity.setUserId(user.getUserId()); toyRepository.save(entity); return ResponseEntity.created( ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(entity.getToyId()).toUri() ).build(); } - @DeleteMapping - @Transactional - public ResponseEntity delete(@RequestBody Toy toy) { - // Bug fix: original code had transaction.rollback() here - now correctly uses @Transactional + @PostMapping("/copy/{toyId}") + public ResponseEntity copy(@PathVariable UUID toyId, Principal principal) { + UserEntity user = userRepository.findByEmail(principal.getName()).orElse(null); + if (user == null) { + return ResponseEntity.status(401).build(); + } + ToyEntity source = toyRepository.findById(toyId).orElse(null); + if (source == null) { + return ResponseEntity.notFound().build(); + } + if (source.getUserId() != null) { + return ResponseEntity.status(403).build(); + } + if (toyRepository.existsByNameIgnoreCaseAndUserId(source.getName(), user.getUserId())) { + return ResponseEntity.status(409) + .header("X-Error", "duplicate-name") + .build(); + } + ToyEntity copy = new ToyEntity(); + copy.setToyId(UUID.randomUUID()); + copy.setName(source.getName()); + copy.setBeschreibung(source.getBeschreibung()); + copy.setUserId(user.getUserId()); + copy.setBild(source.getBild()); + toyRepository.save(copy); + return ResponseEntity.status(201).build(); + } + + @PutMapping("/{toyId}") + public ResponseEntity update(@PathVariable UUID toyId, @RequestBody Toy toy, Principal principal) { + if (toy.getName() == null || toy.getName().isBlank()) { + return ResponseEntity.badRequest().build(); + } + UserEntity user = userRepository.findByEmail(principal.getName()).orElse(null); + if (user == null) { + return ResponseEntity.status(401).build(); + } + ToyEntity entity = toyRepository.findById(toyId).orElse(null); + if (entity == null) { + return ResponseEntity.notFound().build(); + } + if (!user.getUserId().equals(entity.getUserId())) { + return ResponseEntity.status(403).build(); + } + if (toyRepository.existsByNameIgnoreCaseAndUserIdIsNullAndToyIdNot(toy.getName(), toyId) + || toyRepository.existsByNameIgnoreCaseAndUserIdAndToyIdNot(toy.getName(), user.getUserId(), toyId)) { + return ResponseEntity.status(409) + .header("X-Error", "duplicate-name") + .build(); + } + entity.setName(toy.getName().trim()); + entity.setBeschreibung(toy.getBeschreibung()); + if (toy.getBild() != null) { + entity.setBild(Base64.getDecoder().decode(toy.getBild())); + } + toyRepository.save(entity); + return ResponseEntity.ok().build(); + } + + @DeleteMapping("/{toyId}") + public ResponseEntity delete(@PathVariable UUID toyId, Principal principal) { + UserEntity user = userRepository.findByEmail(principal.getName()).orElse(null); + if (user == null) { + return ResponseEntity.status(401).build(); + } + ToyEntity toy = toyRepository.findById(toyId).orElse(null); + if (toy == null) { + return ResponseEntity.noContent().build(); + } + if (!user.getUserId().equals(toy.getUserId())) { + return ResponseEntity.status(403).build(); + } + if (toyRepository.countAufgabeUsage(toyId) > 0 + || toyRepository.countStrafeUsage(toyId) > 0 + || toyRepository.countSperreUsage(toyId) > 0) { + return ResponseEntity.status(409).build(); + } try { - toyRepository.findById(toy.getToyId()).ifPresent(toyRepository::delete); + toyRepository.delete(toy); return ResponseEntity.accepted().build(); - } catch (Exception exception) { - LOGGER.error(exception.getMessage(), exception); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); return ResponseEntity.internalServerError().build(); } } + + private ToyPage toToyPage(Page page) { + ToyPage toyPage = new ToyPage(); + toyPage.setContent(page.getContent().stream().map(ToyEntity::toToy).toList()); + toyPage.setCurrentPage(page.getNumber()); + toyPage.setTotalPages(page.getTotalPages()); + toyPage.setTotalElements(page.getTotalElements()); + return toyPage; + } } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/AufgabeEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/AufgabeEntity.java index 0bde1ef..4c050c4 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/AufgabeEntity.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/AufgabeEntity.java @@ -16,6 +16,7 @@ import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -98,12 +99,12 @@ public class AufgabeEntity { return aufgabe; } - public static AufgabeEntity create(Aufgabe aufgabe, AufgabenGruppeEntity aufgabenGruppeEntity) { + public static AufgabeEntity create(Aufgabe aufgabe, AufgabenGruppeEntity aufgabenGruppeEntity, List toys) { AufgabeEntity entity = new AufgabeEntity(); entity.setAufgabeId(UUID.randomUUID()); entity.setAufgabenGruppe(aufgabenGruppeEntity); entity.setBenoetigtAktiv(aufgabe.getBenoetigtAktiv()); - entity.setBenoetigteToys(aufgabe.getBenoetigteToys().stream().map(toy -> ToyEntity.create(toy, aufgabenGruppeEntity)).toList()); + entity.setBenoetigteToys(toys != null ? toys : new ArrayList<>()); entity.setBenoetigtPassiv(aufgabe.getBenoetigtPassiv()); entity.setKurzText(aufgabe.getKurzText()); entity.setLevel(aufgabe.getLevel()); diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/AufgabenGruppeEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/AufgabenGruppeEntity.java index b233d08..a1893a3 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/AufgabenGruppeEntity.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/AufgabenGruppeEntity.java @@ -33,10 +33,6 @@ public class AufgabenGruppeEntity { private byte[] bild; @Column private String von; - @Column - private Integer relevanz; - @OneToMany(mappedBy = "aufgabenGruppe") - private List toys; @OneToMany(mappedBy = "aufgabenGruppe") private List aufgaben; @OneToMany(mappedBy = "aufgabenGruppe") @@ -65,12 +61,6 @@ public class AufgabenGruppeEntity { public String getVon() { return von; } public void setVon(String von) { this.von = von; } - public Integer getRelevanz() { return relevanz; } - public void setRelevanz(Integer relevanz) { this.relevanz = relevanz; } - - public List getToys() { return toys; } - public void setToys(List toys) { this.toys = toys; } - public List getAufgaben() { return aufgaben; } public void setAufgaben(List aufgaben) { this.aufgaben = aufgaben; } @@ -89,7 +79,6 @@ public class AufgabenGruppeEntity { gruppe.setPrivateGruppe(privateGruppe); gruppe.setBild(bild != null ? Base64.getEncoder().encodeToString(bild) : null); gruppe.setVon(von); - gruppe.setToys(toys.stream().map(ToyEntity::toToy).toList()); gruppe.setAufgaben(aufgaben.stream().map(AufgabeEntity::toAufgabe).toList()); gruppe.setStrafen(strafen.stream().map(StrafeEntity::toStrafe).toList()); gruppe.setSperren(sperren.stream().map(SperreEntity::toSperre).toList()); diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/GruppenAboEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/GruppenAboEntity.java new file mode 100644 index 0000000..9c38f11 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/GruppenAboEntity.java @@ -0,0 +1,35 @@ +package de.oaa.xxx.aufgaben.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; + +import java.util.UUID; + +@Entity +@Table(name = "gruppen_abo") +public class GruppenAboEntity { + + @Id + @Column + private UUID aboId; + + @Column + private UUID userId; + + @ManyToOne + @JoinColumn(name = "gruppenId") + private AufgabenGruppeEntity aufgabenGruppe; + + public UUID getAboId() { return aboId; } + public void setAboId(UUID aboId) { this.aboId = aboId; } + + public UUID getUserId() { return userId; } + public void setUserId(UUID userId) { this.userId = userId; } + + public AufgabenGruppeEntity getAufgabenGruppe() { return aufgabenGruppe; } + public void setAufgabenGruppe(AufgabenGruppeEntity aufgabenGruppe) { this.aufgabenGruppe = aufgabenGruppe; } +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/SperreEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/SperreEntity.java index 9933bf4..0a5186c 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/SperreEntity.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/SperreEntity.java @@ -16,6 +16,7 @@ import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -85,14 +86,15 @@ public class SperreEntity { sperre.setReleaseText(releaseText); sperre.setSperreFuer(sperreFuer); sperre.setText(text); + sperre.setBenoetigteToys(benoetigteToys != null ? benoetigteToys.stream().map(ToyEntity::toToy).toList() : new ArrayList<>()); return sperre; } - public static SperreEntity create(Sperre sperre, AufgabenGruppeEntity aufgabenGruppeEntity) { + public static SperreEntity create(Sperre sperre, AufgabenGruppeEntity aufgabenGruppeEntity, List toys) { SperreEntity entity = new SperreEntity(); entity.setSperreId(UUID.randomUUID()); entity.setAufgabenGruppe(aufgabenGruppeEntity); - entity.setBenoetigteToys(sperre.getBenoetigteToys().stream().map(toy -> ToyEntity.create(toy, aufgabenGruppeEntity)).toList()); + entity.setBenoetigteToys(toys != null ? toys : new ArrayList<>()); entity.setKurzText(sperre.getKurzText()); entity.setMinutenBis(sperre.getMinutenBis()); entity.setMinutenVon(sperre.getMinutenVon()); diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/StrafeEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/StrafeEntity.java index 88be823..3ef2955 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/StrafeEntity.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/StrafeEntity.java @@ -16,6 +16,7 @@ import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -98,12 +99,12 @@ public class StrafeEntity { return strafe; } - public static StrafeEntity create(Strafe strafe, AufgabenGruppeEntity aufgabenGruppeEntity) { + public static StrafeEntity create(Strafe strafe, AufgabenGruppeEntity aufgabenGruppeEntity, List toys) { StrafeEntity entity = new StrafeEntity(); entity.setStrafeId(UUID.randomUUID()); entity.setAufgabenGruppe(aufgabenGruppeEntity); entity.setBenoetigtAktiv(strafe.getBenoetigtAktiv()); - entity.setBenoetigteToys(strafe.getBenoetigteToys().stream().map(toy -> ToyEntity.create(toy, aufgabenGruppeEntity)).toList()); + entity.setBenoetigteToys(toys != null ? toys : new ArrayList<>()); entity.setBenoetigtPassiv(strafe.getBenoetigtPassiv()); entity.setKurzText(strafe.getKurzText()); entity.setLevel(strafe.getLevel()); diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/ToyEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/ToyEntity.java index 5d3926a..6fc2025 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/ToyEntity.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/ToyEntity.java @@ -4,10 +4,10 @@ import de.oaa.xxx.aufgaben.Toy; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; +import jakarta.persistence.Lob; import jakarta.persistence.Table; +import java.util.Base64; import java.util.UUID; @Entity @@ -21,9 +21,11 @@ public class ToyEntity { private String name; @Column private String beschreibung; - @ManyToOne - @JoinColumn(name = "gruppeId") - private AufgabenGruppeEntity aufgabenGruppe; + @Column + private UUID userId; + @Lob + @Column(columnDefinition = "BLOB") + private byte[] bild; public UUID getToyId() { return toyId; } public void setToyId(UUID toyId) { this.toyId = toyId; } @@ -34,24 +36,29 @@ public class ToyEntity { public String getBeschreibung() { return beschreibung; } public void setBeschreibung(String beschreibung) { this.beschreibung = beschreibung; } - public AufgabenGruppeEntity getAufgabenGruppe() { return aufgabenGruppe; } - public void setAufgabenGruppe(AufgabenGruppeEntity aufgabenGruppe) { this.aufgabenGruppe = aufgabenGruppe; } + public UUID getUserId() { return userId; } + public void setUserId(UUID userId) { this.userId = userId; } + + public byte[] getBild() { return bild; } + public void setBild(byte[] bild) { this.bild = bild; } public Toy toToy() { Toy toy = new Toy(); - toy.setBeschreibung(beschreibung); - toy.setName(name); - toy.setGruppeId(aufgabenGruppe.getGruppenId()); toy.setToyId(toyId); + toy.setName(name); + toy.setBeschreibung(beschreibung); + toy.setUserId(userId); + toy.setBild(bild != null ? Base64.getEncoder().encodeToString(bild) : null); return toy; } - public static ToyEntity create(Toy toy, AufgabenGruppeEntity aufgabenGruppeEntity) { + public static ToyEntity create(Toy toy) { ToyEntity entity = new ToyEntity(); - entity.setAufgabenGruppe(aufgabenGruppeEntity); - entity.setBeschreibung(toy.getBeschreibung()); - entity.setName(toy.getName()); entity.setToyId(UUID.randomUUID()); + entity.setName(toy.getName()); + entity.setBeschreibung(toy.getBeschreibung()); + entity.setUserId(toy.getUserId()); + entity.setBild(toy.getBild() != null ? Base64.getDecoder().decode(toy.getBild()) : null); return entity; } } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/repository/AufgabenGruppeRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/repository/AufgabenGruppeRepository.java index aaccd61..2fae55f 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/repository/AufgabenGruppeRepository.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/repository/AufgabenGruppeRepository.java @@ -1,7 +1,9 @@ package de.oaa.xxx.aufgaben.repository; import de.oaa.xxx.aufgaben.entity.AufgabenGruppeEntity; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -14,9 +16,18 @@ public interface AufgabenGruppeRepository extends JpaRepository findByUserId(@Param("userId") UUID userId); + long countByUserId(UUID userId); + + Page findByUserIdIsNull(Pageable pageable); + + Page findByUserId(UUID userId, Pageable pageable); + @Query("select age from AufgabenGruppeEntity age where (age.privateGruppe = false or age.userId = :userId) and (:search is null or age.name like :search)") List listWithUserAndSearch(@Param("userId") UUID userId, @Param("search") String search, PageRequest pageable); @Query("select age from AufgabenGruppeEntity age where age.privateGruppe = false and (:search is null or age.name like :search)") List listPublicWithSearch(@Param("search") String search, PageRequest pageable); + + @Query("select age from AufgabenGruppeEntity age where age.privateGruppe = false and age.userId is not null and age.userId <> :userId and (:name is null or lower(age.name) like lower(:name))") + List findPublicFromOthers(@Param("userId") UUID userId, @Param("name") String name); } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/repository/GruppenAboRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/repository/GruppenAboRepository.java new file mode 100644 index 0000000..4dadd19 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/repository/GruppenAboRepository.java @@ -0,0 +1,21 @@ +package de.oaa.xxx.aufgaben.repository; + +import de.oaa.xxx.aufgaben.entity.AufgabenGruppeEntity; +import de.oaa.xxx.aufgaben.entity.GruppenAboEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.UUID; + +public interface GruppenAboRepository extends JpaRepository { + + List findByUserId(UUID userId); + + boolean existsByUserIdAndAufgabenGruppe(UUID userId, AufgabenGruppeEntity gruppe); + + void deleteByUserIdAndAufgabenGruppe(UUID userId, AufgabenGruppeEntity gruppe); + + long countByAufgabenGruppe(AufgabenGruppeEntity gruppe); + + void deleteByAufgabenGruppe(AufgabenGruppeEntity gruppe); +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/repository/ToyRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/repository/ToyRepository.java index 94ae405..4f92fe8 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/repository/ToyRepository.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/repository/ToyRepository.java @@ -1,9 +1,37 @@ package de.oaa.xxx.aufgaben.repository; import de.oaa.xxx.aufgaben.entity.ToyEntity; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import java.util.Optional; import java.util.UUID; public interface ToyRepository extends JpaRepository { + + Page findByUserIdIsNull(Pageable pageable); + + Page findByUserId(UUID userId, Pageable pageable); + + boolean existsByNameIgnoreCaseAndUserIdIsNull(String name); + + boolean existsByNameIgnoreCaseAndUserId(String name, UUID userId); + + boolean existsByNameIgnoreCaseAndUserIdIsNullAndToyIdNot(String name, UUID toyId); + + boolean existsByNameIgnoreCaseAndUserIdAndToyIdNot(String name, UUID userId, UUID toyId); + + Optional findByNameIgnoreCaseAndUserId(String name, UUID userId); + + @Query("SELECT COUNT(a) FROM AufgabeEntity a JOIN a.benoetigteToys t WHERE t.toyId = :toyId") + long countAufgabeUsage(@Param("toyId") UUID toyId); + + @Query("SELECT COUNT(s) FROM StrafeEntity s JOIN s.benoetigteToys t WHERE t.toyId = :toyId") + long countStrafeUsage(@Param("toyId") UUID toyId); + + @Query("SELECT COUNT(sp) FROM SperreEntity sp JOIN sp.benoetigteToys t WHERE t.toyId = :toyId") + long countSperreUsage(@Param("toyId") UUID toyId); } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/config/JwtFilter.java b/xxxthegame/src/main/java/de/oaa/xxx/config/JwtFilter.java new file mode 100644 index 0000000..ab27a0c --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/config/JwtFilter.java @@ -0,0 +1,48 @@ +package de.oaa.xxx.config; + +import io.jsonwebtoken.Claims; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.Collections; + +@Component +public class JwtFilter extends OncePerRequestFilter { + + private final JwtService jwtService; + + public JwtFilter(JwtService jwtService) { + this.jwtService = jwtService; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if ("jwt".equals(cookie.getName())) { + try { + Claims claims = jwtService.validateAndGetClaims(cookie.getValue()); + UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken( + claims.getSubject(), null, Collections.emptyList() + ); + SecurityContextHolder.getContext().setAuthentication(auth); + } catch (Exception e) { + // Ungültiger oder abgelaufener Token – ohne Authentifizierung weiter + } + break; + } + } + } + chain.doFilter(request, response); + } +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/config/JwtService.java b/xxxthegame/src/main/java/de/oaa/xxx/config/JwtService.java new file mode 100644 index 0000000..9ea02e9 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/config/JwtService.java @@ -0,0 +1,49 @@ +package de.oaa.xxx.config; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Service; + +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Date; + +@Service +public class JwtService { + + private static final long EXPIRATION_MS = 24L * 60 * 60 * 1000; // 24 Stunden + + private final PrivateKey privateKey; + private final PublicKey publicKey; + + public JwtService( + @Value("${jwt.keystore.path}") Resource keystoreResource, + @Value("${jwt.keystore.password}") String password, + @Value("${jwt.keystore.alias}") String alias) throws Exception { + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(keystoreResource.getInputStream(), password.toCharArray()); + this.privateKey = (PrivateKey) keyStore.getKey(alias, password.toCharArray()); + this.publicKey = keyStore.getCertificate(alias).getPublicKey(); + } + + public String generateToken(String email, String name) { + return Jwts.builder() + .subject(email) + .claim("name", name) + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + EXPIRATION_MS)) + .signWith(privateKey) + .compact(); + } + + public Claims validateAndGetClaims(String token) { + return Jwts.parser() + .verifyWith(publicKey) + .build() + .parseSignedClaims(token) + .getPayload(); + } +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/config/SecurityConfig.java b/xxxthegame/src/main/java/de/oaa/xxx/config/SecurityConfig.java index be0e353..a5c52b1 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/config/SecurityConfig.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/config/SecurityConfig.java @@ -8,14 +8,17 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration @EnableWebSecurity public class SecurityConfig { - public SecurityConfig() { + private final JwtFilter jwtFilter; + public SecurityConfig(JwtFilter jwtFilter) { + this.jwtFilter = jwtFilter; } @Bean @@ -23,9 +26,16 @@ public class SecurityConfig { http .csrf(AbstractHttpConfigurer::disable) .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .exceptionHandling(ex -> ex + .authenticationEntryPoint((request, response, authException) -> + response.sendRedirect("/login.html"))) .authorizeHttpRequests(auth -> auth .requestMatchers(AntPathRequestMatcher.antMatcher("/")).permitAll() .requestMatchers(AntPathRequestMatcher.antMatcher("/error")).permitAll() + .requestMatchers(AntPathRequestMatcher.antMatcher("/userhome.html")).authenticated() + .requestMatchers(AntPathRequestMatcher.antMatcher("/toys.html")).authenticated() + .requestMatchers(AntPathRequestMatcher.antMatcher("/aufgaben.html")).authenticated() + .requestMatchers(AntPathRequestMatcher.antMatcher("/entdecken.html")).authenticated() .requestMatchers(AntPathRequestMatcher.antMatcher("/*.html")).permitAll() .requestMatchers(AntPathRequestMatcher.antMatcher("/css/**")).permitAll() .requestMatchers(AntPathRequestMatcher.antMatcher("/js/**")).permitAll() @@ -40,7 +50,8 @@ public class SecurityConfig { .requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/activation/**")).permitAll() .requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.POST, "/filler")).permitAll() .anyRequest().authenticated() - ); + ) + .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/user/LoginController.java b/xxxthegame/src/main/java/de/oaa/xxx/user/LoginController.java index 0096d5d..11b4f87 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/user/LoginController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/user/LoginController.java @@ -1,10 +1,11 @@ package de.oaa.xxx.user; -import java.util.Optional; -import java.util.UUID; - +import de.oaa.xxx.config.JwtService; +import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -12,7 +13,10 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import jakarta.servlet.http.HttpServletRequest; +import java.security.Principal; +import java.time.Duration; +import java.util.Optional; +import java.util.UUID; @RestController @RequestMapping("/login") @@ -21,23 +25,43 @@ public class LoginController { private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class); private final UserRepository userRepository; + private final JwtService jwtService; - public LoginController(UserRepository userRepository) { + public LoginController(UserRepository userRepository, JwtService jwtService) { this.userRepository = userRepository; + this.jwtService = jwtService; } @GetMapping public ResponseEntity login(@RequestParam String email, @RequestParam String hash, - HttpServletRequest req) { + HttpServletResponse response) { Optional user = userRepository.findByEmailAndPassword(email, hash); if (user.isPresent()) { LOGGER.info("User erfolgreich angemeldet: {}", email); + String token = jwtService.generateToken(user.get().getEmail(), user.get().getName()); + ResponseCookie cookie = ResponseCookie.from("jwt", token) + .httpOnly(true) + .sameSite("Strict") + .path("/") + .maxAge(Duration.ofHours(24)) + .build(); + response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); return ResponseEntity.ok(user.get().toUser()); } else { return ResponseEntity.noContent().build(); } } + @GetMapping("/me") + public ResponseEntity me(Principal principal) { + if (principal == null) { + return ResponseEntity.status(401).build(); + } + return userRepository.findByEmail(principal.getName()) + .map(entity -> ResponseEntity.ok(entity.toUser())) + .orElse(ResponseEntity.status(401).build()); + } + @GetMapping("/{userId}") public ResponseEntity get(@PathVariable UUID userId) { return userRepository.findById(userId) diff --git a/xxxthegame/src/main/resources/static/aufgaben.html b/xxxthegame/src/main/resources/static/aufgaben.html new file mode 100644 index 0000000..9ccd053 --- /dev/null +++ b/xxxthegame/src/main/resources/static/aufgaben.html @@ -0,0 +1,1584 @@ + + + + + + Aufgaben – XXX The Game + + + + + + +
+ + + + + + + + + + + + + +
+ + +
+
+ +

Aufgaben

+
+ +
+ + +
+
+

Meine Aufgabengruppen

+
+ + + + +
+
+
+
Wird geladen…
+
+ +
+ + +
+
+

Abonnierte Aufgabengruppen

+
+ + +
+
+
+
Wird geladen…
+
+ +
+ + +
+
+

System-Aufgabengruppen

+
+ +
+
+
+
Wird geladen…
+
+ +
+ +
+
+
+ + + + diff --git a/xxxthegame/src/main/resources/static/entdecken.html b/xxxthegame/src/main/resources/static/entdecken.html new file mode 100644 index 0000000..c1e2e36 --- /dev/null +++ b/xxxthegame/src/main/resources/static/entdecken.html @@ -0,0 +1,603 @@ + + + + + + Entdecken – XXX The Game + + + + + + +
+ +
+ + +
+
+ +

Entdecken

+
+ +
+ +
Wird geladen…
+
+ +
+
+
+ + + + diff --git a/xxxthegame/src/main/resources/static/toys.html b/xxxthegame/src/main/resources/static/toys.html new file mode 100644 index 0000000..4ef3187 --- /dev/null +++ b/xxxthegame/src/main/resources/static/toys.html @@ -0,0 +1,780 @@ + + + + + + Toys – XXX The Game + + + + + + +
+ + + + +
+ + + +
+
+ +

Toys

+
+ +
+ + +
+
+

Meine Toys

+
+ + + +
+
+
+
Wird geladen…
+
+ +
+ + +
+
+

System-Toys

+
+ +
+
+
+
Wird geladen…
+
+ +
+ +
+
+
+ + + + diff --git a/xxxthegame/src/main/resources/static/userhome.html b/xxxthegame/src/main/resources/static/userhome.html index 9e9515a..ff88286 100644 --- a/xxxthegame/src/main/resources/static/userhome.html +++ b/xxxthegame/src/main/resources/static/userhome.html @@ -6,19 +6,274 @@ XXX The Game + -

XXX The Game

-

+ +
+ +
+ + + +
+
+ +

XXX The Game

+
+ +
+

+
+
+ +