Neues Feature für Vorlieben hinzugeüft, Bugfixes im Vanilla game
@@ -72,7 +72,8 @@
|
|||||||
"Bash(mv bdsm-einladung.html games/bdsm/)",
|
"Bash(mv bdsm-einladung.html games/bdsm/)",
|
||||||
"Bash(mv bdsmingame.html games/bdsm/)",
|
"Bash(mv bdsmingame.html games/bdsm/)",
|
||||||
"Bash(mv bdsmplayers.html games/bdsm/)",
|
"Bash(mv bdsmplayers.html games/bdsm/)",
|
||||||
"Bash(perl -pi -e 's|\\\\.requestMatchers\\\\\\(\"\"/\\\\*\\\\.html\"\"\\\\\\)\\\\.permitAll\\\\\\(\\\\\\)|.requestMatchers\\(\"\"/*.html\"\"\\).permitAll\\(\\)\\\\n .requestMatchers\\(\"\"/**/*.html\"\"\\).permitAll\\(\\)|' /home/mario/Workspaces/xxx-thegame/xxxthegame/src/main/java/de/oaa/xxx/config/SecurityConfig.java)"
|
"Bash(perl -pi -e 's|\\\\.requestMatchers\\\\\\(\"\"/\\\\*\\\\.html\"\"\\\\\\)\\\\.permitAll\\\\\\(\\\\\\)|.requestMatchers\\(\"\"/*.html\"\"\\).permitAll\\(\\)\\\\n .requestMatchers\\(\"\"/**/*.html\"\"\\).permitAll\\(\\)|' /home/mario/Workspaces/xxx-thegame/xxxthegame/src/main/java/de/oaa/xxx/config/SecurityConfig.java)",
|
||||||
|
"Bash(./gradlew compileJava -x test)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#Mon Mar 30 07:33:01 CEST 2026
|
#Tue Mar 31 20:02:52 CEST 2026
|
||||||
display=\:0
|
display=\:0
|
||||||
host=mario-mint
|
host=mario-mint
|
||||||
process-id=2955
|
process-id=9888
|
||||||
user=mario
|
user=mario
|
||||||
|
|||||||
174
.metadata/.log
@@ -1379,3 +1379,177 @@ Binding(CTRL+SHIFT+T,
|
|||||||
,,true),null),
|
,,true),null),
|
||||||
org.eclipse.ui.defaultAcceleratorConfiguration,
|
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||||
org.eclipse.ui.contexts.window,,,system)
|
org.eclipse.ui.contexts.window,,,system)
|
||||||
|
|
||||||
|
!ENTRY org.springframework.tooling.boot.ls 1 0 2026-03-30 22:56:58.269
|
||||||
|
!MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS
|
||||||
|
!SESSION 2026-03-31 08:24:30.017 -----------------------------------------------
|
||||||
|
eclipse.buildId=4.39.0.20260305-0817
|
||||||
|
java.version=21.0.6
|
||||||
|
java.vendor=Eclipse Adoptium
|
||||||
|
BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=de_DE
|
||||||
|
Framework arguments: -product org.eclipse.epp.package.java.product
|
||||||
|
Command-line arguments: -os linux -ws gtk -arch x86_64 -clean -product org.eclipse.epp.package.java.product
|
||||||
|
|
||||||
|
!ENTRY ch.qos.logback.classic 1 0 2026-03-31 08:24:31.551
|
||||||
|
!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized.
|
||||||
|
|
||||||
|
!ENTRY ch.qos.logback.classic 1 0 2026-03-31 08:24:34.912
|
||||||
|
!MESSAGE Logback config file: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.m2e.logback/logback.2.7.101.20251017-1242.xml
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.ui 2 0 2026-03-31 08:24:35.071
|
||||||
|
!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
|
||||||
|
!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-31 08:24:35.071
|
||||||
|
!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.ui 2 0 2026-03-31 08:24:35.221
|
||||||
|
!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
|
||||||
|
!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-31 08:24:35.221
|
||||||
|
!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.jface 2 0 2026-03-31 09:50:47.663
|
||||||
|
!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation.
|
||||||
|
!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-31 09:50:47.663
|
||||||
|
!MESSAGE A conflict occurred for CTRL+SHIFT+T:
|
||||||
|
Binding(CTRL+SHIFT+T,
|
||||||
|
ParameterizedCommand(Command(org.eclipse.jdt.ui.navigate.open.type,Open Type,
|
||||||
|
Open a type in a Java editor,
|
||||||
|
Category(org.eclipse.ui.category.navigate,Navigate,null,true),
|
||||||
|
WorkbenchHandlerServiceHandler("org.eclipse.jdt.ui.navigate.open.type"),
|
||||||
|
,,true),null),
|
||||||
|
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||||
|
org.eclipse.ui.contexts.window,,,system)
|
||||||
|
Binding(CTRL+SHIFT+T,
|
||||||
|
ParameterizedCommand(Command(org.eclipse.lsp4e.symbolInWorkspace,Go to Symbol in Workspace,
|
||||||
|
,
|
||||||
|
Category(org.eclipse.lsp4e.category,Language Servers,null,true),
|
||||||
|
WorkbenchHandlerServiceHandler("org.eclipse.lsp4e.symbolInWorkspace"),
|
||||||
|
,,true),null),
|
||||||
|
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||||
|
org.eclipse.ui.contexts.window,,,system)
|
||||||
|
|
||||||
|
!ENTRY org.springframework.tooling.boot.ls 1 0 2026-03-31 10:19:59.888
|
||||||
|
!MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS
|
||||||
|
!SESSION 2026-03-31 11:33:04.112 -----------------------------------------------
|
||||||
|
eclipse.buildId=4.39.0.20260305-0817
|
||||||
|
java.version=21.0.6
|
||||||
|
java.vendor=Eclipse Adoptium
|
||||||
|
BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=de_DE
|
||||||
|
Framework arguments: -product org.eclipse.epp.package.java.product
|
||||||
|
Command-line arguments: -os linux -ws gtk -arch x86_64 -clean -product org.eclipse.epp.package.java.product
|
||||||
|
|
||||||
|
!ENTRY ch.qos.logback.classic 1 0 2026-03-31 11:33:05.636
|
||||||
|
!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized.
|
||||||
|
|
||||||
|
!ENTRY ch.qos.logback.classic 1 0 2026-03-31 11:33:15.062
|
||||||
|
!MESSAGE Logback config file: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.m2e.logback/logback.2.7.101.20251017-1242.xml
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.ui 2 0 2026-03-31 11:33:15.221
|
||||||
|
!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
|
||||||
|
!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-31 11:33:15.221
|
||||||
|
!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.ui 2 0 2026-03-31 11:33:15.366
|
||||||
|
!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
|
||||||
|
!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-31 11:33:15.366
|
||||||
|
!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.jface 2 0 2026-03-31 11:35:22.321
|
||||||
|
!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation.
|
||||||
|
!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-31 11:35:22.321
|
||||||
|
!MESSAGE A conflict occurred for CTRL+SHIFT+T:
|
||||||
|
Binding(CTRL+SHIFT+T,
|
||||||
|
ParameterizedCommand(Command(org.eclipse.jdt.ui.navigate.open.type,Open Type,
|
||||||
|
Open a type in a Java editor,
|
||||||
|
Category(org.eclipse.ui.category.navigate,Navigate,null,true),
|
||||||
|
WorkbenchHandlerServiceHandler("org.eclipse.jdt.ui.navigate.open.type"),
|
||||||
|
,,true),null),
|
||||||
|
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||||
|
org.eclipse.ui.contexts.window,,,system)
|
||||||
|
Binding(CTRL+SHIFT+T,
|
||||||
|
ParameterizedCommand(Command(org.eclipse.lsp4e.symbolInWorkspace,Go to Symbol in Workspace,
|
||||||
|
,
|
||||||
|
Category(org.eclipse.lsp4e.category,Language Servers,null,true),
|
||||||
|
WorkbenchHandlerServiceHandler("org.eclipse.lsp4e.symbolInWorkspace"),
|
||||||
|
,,true),null),
|
||||||
|
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||||
|
org.eclipse.ui.contexts.window,,,system)
|
||||||
|
|
||||||
|
!ENTRY org.springframework.tooling.boot.ls 1 0 2026-03-31 15:25:37.605
|
||||||
|
!MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS
|
||||||
|
!SESSION 2026-03-31 20:02:49.357 -----------------------------------------------
|
||||||
|
eclipse.buildId=4.39.0.20260305-0817
|
||||||
|
java.version=21.0.6
|
||||||
|
java.vendor=Eclipse Adoptium
|
||||||
|
BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=de_DE
|
||||||
|
Framework arguments: -product org.eclipse.epp.package.java.product
|
||||||
|
Command-line arguments: -os linux -ws gtk -arch x86_64 -clean -product org.eclipse.epp.package.java.product
|
||||||
|
|
||||||
|
!ENTRY ch.qos.logback.classic 1 0 2026-03-31 20:02:50.906
|
||||||
|
!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized.
|
||||||
|
|
||||||
|
!ENTRY ch.qos.logback.classic 1 0 2026-03-31 20:02:53.313
|
||||||
|
!MESSAGE Logback config file: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.m2e.logback/logback.2.7.101.20251017-1242.xml
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.ui 2 0 2026-03-31 20:02:53.439
|
||||||
|
!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
|
||||||
|
!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-31 20:02:53.440
|
||||||
|
!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.ui 2 0 2026-03-31 20:02:53.586
|
||||||
|
!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
|
||||||
|
!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-31 20:02:53.586
|
||||||
|
!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.jface 2 0 2026-03-31 20:48:38.922
|
||||||
|
!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation.
|
||||||
|
!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-31 20:48:38.922
|
||||||
|
!MESSAGE A conflict occurred for CTRL+SHIFT+T:
|
||||||
|
Binding(CTRL+SHIFT+T,
|
||||||
|
ParameterizedCommand(Command(org.eclipse.jdt.ui.navigate.open.type,Open Type,
|
||||||
|
Open a type in a Java editor,
|
||||||
|
Category(org.eclipse.ui.category.navigate,Navigate,null,true),
|
||||||
|
WorkbenchHandlerServiceHandler("org.eclipse.jdt.ui.navigate.open.type"),
|
||||||
|
,,true),null),
|
||||||
|
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||||
|
org.eclipse.ui.contexts.window,,,system)
|
||||||
|
Binding(CTRL+SHIFT+T,
|
||||||
|
ParameterizedCommand(Command(org.eclipse.lsp4e.symbolInWorkspace,Go to Symbol in Workspace,
|
||||||
|
,
|
||||||
|
Category(org.eclipse.lsp4e.category,Language Servers,null,true),
|
||||||
|
WorkbenchHandlerServiceHandler("org.eclipse.lsp4e.symbolInWorkspace"),
|
||||||
|
,,true),null),
|
||||||
|
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||||
|
org.eclipse.ui.contexts.window,,,system)
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.lsp4e 2 0 2026-03-31 21:38:55.972
|
||||||
|
!MESSAGE Javadoc unavailable. Failed to obtain it.
|
||||||
|
!STACK 0
|
||||||
|
java.lang.InterruptedException
|
||||||
|
at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:386)
|
||||||
|
at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2096)
|
||||||
|
at org.eclipse.lsp4e.jdt.LSJavaHoverProvider.getHoverInfo2(LSJavaHoverProvider.java:66)
|
||||||
|
at org.eclipse.jdt.internal.ui.text.java.hover.BestMatchHover.getHoverInfo2(BestMatchHover.java:165)
|
||||||
|
at org.eclipse.jdt.internal.ui.text.java.hover.BestMatchHover.getHoverInfo2(BestMatchHover.java:131)
|
||||||
|
at org.eclipse.jdt.internal.ui.text.java.hover.JavaEditorTextHoverProxy.getHoverInfo2(JavaEditorTextHoverProxy.java:89)
|
||||||
|
at org.eclipse.jface.text.TextViewerHoverManager$1.run(TextViewerHoverManager.java:155)
|
||||||
|
|
||||||
|
!ENTRY org.eclipse.jface 2 0 2026-03-31 22:11:43.432
|
||||||
|
!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation.
|
||||||
|
!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-31 22:11:43.432
|
||||||
|
!MESSAGE A conflict occurred for CTRL+R:
|
||||||
|
Binding(CTRL+R,
|
||||||
|
ParameterizedCommand(Command(org.eclipse.debug.ui.commands.RunToLine,Run to Line,
|
||||||
|
Resume and break when execution reaches the current line,
|
||||||
|
Category(org.eclipse.debug.ui.category.run,Run/Debug,Run/Debug command category,true),
|
||||||
|
WorkbenchHandlerServiceHandler("org.eclipse.debug.ui.commands.RunToLine"),
|
||||||
|
,,true),null),
|
||||||
|
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||||
|
org.eclipse.debug.ui.debugging,,,system)
|
||||||
|
Binding(CTRL+R,
|
||||||
|
ParameterizedCommand(Command(org.springframework.ide.eclipse.boot.restart.commands.restart,Trigger Restart,
|
||||||
|
Restart Spring Boot Application,
|
||||||
|
Category(org.eclipse.debug.ui.category.run,Run/Debug,Run/Debug command category,true),
|
||||||
|
WorkbenchHandlerServiceHandler("org.springframework.ide.eclipse.boot.restart.commands.restart"),
|
||||||
|
,,true),null),
|
||||||
|
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||||
|
org.eclipse.debug.ui.console,,,system)
|
||||||
|
|||||||
@@ -1,24 +1,7 @@
|
|||||||
[ {
|
[ {
|
||||||
"version" : "9.6.0-20260329003549+0000",
|
"version" : "9.5.0-20260331054436+0000",
|
||||||
"buildTime" : "20260329003549+0000",
|
"buildTime" : "20260331054436+0000",
|
||||||
"commitId" : "db62c2f2b404217cb6a7eef2598c6e84ab08fa27",
|
"commitId" : "04cdc7917382feb3229f13b035ea48f106ad01f1",
|
||||||
"current" : false,
|
|
||||||
"snapshot" : true,
|
|
||||||
"nightly" : true,
|
|
||||||
"releaseNightly" : false,
|
|
||||||
"activeRc" : false,
|
|
||||||
"rcFor" : "",
|
|
||||||
"milestoneFor" : "",
|
|
||||||
"broken" : false,
|
|
||||||
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260329003549+0000-bin.zip",
|
|
||||||
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260329003549+0000-bin.zip.sha256",
|
|
||||||
"checksum" : "27b9c08aeaf720b9ee44dc6eef5543699bafba27772aa8a33e64cf964a2ea958",
|
|
||||||
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260329003549+0000-wrapper.jar.sha256",
|
|
||||||
"wrapperChecksum" : "497c8c2a7e5031f6aa847f88104aa80a93532ec32ee17bdb8d1d2f67a194a9c7"
|
|
||||||
}, {
|
|
||||||
"version" : "9.5.0-20260328024422+0000",
|
|
||||||
"buildTime" : "20260328024422+0000",
|
|
||||||
"commitId" : "a90300e5c547f6d0416d765f1ef285d1ecb589f9",
|
|
||||||
"current" : false,
|
"current" : false,
|
||||||
"snapshot" : true,
|
"snapshot" : true,
|
||||||
"nightly" : false,
|
"nightly" : false,
|
||||||
@@ -27,10 +10,44 @@
|
|||||||
"rcFor" : "",
|
"rcFor" : "",
|
||||||
"milestoneFor" : "",
|
"milestoneFor" : "",
|
||||||
"broken" : false,
|
"broken" : false,
|
||||||
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260328024422+0000-bin.zip",
|
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260331054436+0000-bin.zip",
|
||||||
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260328024422+0000-bin.zip.sha256",
|
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260331054436+0000-bin.zip.sha256",
|
||||||
"checksum" : "f921ed9b701b2046ba53c3c499df12ebab7b70b6d58c83337d9375427f9af2ee",
|
"checksum" : "4ab20ff318524006769da0e39fa7cf8f355a5ca54ea213dd2edadd9019d95649",
|
||||||
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260328024422+0000-wrapper.jar.sha256",
|
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260331054436+0000-wrapper.jar.sha256",
|
||||||
|
"wrapperChecksum" : "497c8c2a7e5031f6aa847f88104aa80a93532ec32ee17bdb8d1d2f67a194a9c7"
|
||||||
|
}, {
|
||||||
|
"version" : "9.6.0-20260331012943+0000",
|
||||||
|
"buildTime" : "20260331012943+0000",
|
||||||
|
"commitId" : "6921c9df28f41760c3a348e57a9bf332d093742e",
|
||||||
|
"current" : false,
|
||||||
|
"snapshot" : true,
|
||||||
|
"nightly" : true,
|
||||||
|
"releaseNightly" : false,
|
||||||
|
"activeRc" : false,
|
||||||
|
"rcFor" : "",
|
||||||
|
"milestoneFor" : "",
|
||||||
|
"broken" : false,
|
||||||
|
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260331012943+0000-bin.zip",
|
||||||
|
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260331012943+0000-bin.zip.sha256",
|
||||||
|
"checksum" : "83f35f9ee38851b1835842ba93b9846fc43be77792c2cae138570c5f262039e0",
|
||||||
|
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260331012943+0000-wrapper.jar.sha256",
|
||||||
|
"wrapperChecksum" : "497c8c2a7e5031f6aa847f88104aa80a93532ec32ee17bdb8d1d2f67a194a9c7"
|
||||||
|
}, {
|
||||||
|
"version" : "9.5.0-rc-1",
|
||||||
|
"buildTime" : "20260330120715+0000",
|
||||||
|
"commitId" : "6a1704c113de068f7e9a6744245c7eb4bc5091d0",
|
||||||
|
"current" : false,
|
||||||
|
"snapshot" : false,
|
||||||
|
"nightly" : false,
|
||||||
|
"releaseNightly" : false,
|
||||||
|
"activeRc" : true,
|
||||||
|
"rcFor" : "9.5.0",
|
||||||
|
"milestoneFor" : "",
|
||||||
|
"broken" : false,
|
||||||
|
"downloadUrl" : "https://services.gradle.org/distributions/gradle-9.5.0-rc-1-bin.zip",
|
||||||
|
"checksumUrl" : "https://services.gradle.org/distributions/gradle-9.5.0-rc-1-bin.zip.sha256",
|
||||||
|
"checksum" : "66d79b10eb939c954bf1ac3be9d9cde985301b56058d49542286c35782ae1e74",
|
||||||
|
"wrapperChecksumUrl" : "https://services.gradle.org/distributions/gradle-9.5.0-rc-1-wrapper.jar.sha256",
|
||||||
"wrapperChecksum" : "497c8c2a7e5031f6aa847f88104aa80a93532ec32ee17bdb8d1d2f67a194a9c7"
|
"wrapperChecksum" : "497c8c2a7e5031f6aa847f88104aa80a93532ec32ee17bdb8d1d2f67a194a9c7"
|
||||||
}, {
|
}, {
|
||||||
"version" : "9.4.1",
|
"version" : "9.4.1",
|
||||||
|
|||||||
@@ -6,13 +6,15 @@
|
|||||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.cardlock{CardLockEntity.java[CardLockEntity" modifiers="1" timestamp="1774814915718"/>
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.cardlock{CardLockEntity.java[CardLockEntity" modifiers="1" timestamp="1774814915718"/>
|
||||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.user{UserController.java[UserController" modifiers="1" timestamp="1774814915722"/>
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.user{UserController.java[UserController" modifiers="1" timestamp="1774814915722"/>
|
||||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.ttlock{TTLockService.java[TTLockService" modifiers="1" timestamp="1774814915716"/>
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.ttlock{TTLockService.java[TTLockService" modifiers="1" timestamp="1774814915716"/>
|
||||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.timelock{TimeLockController.java[TimeLockController" modifiers="1" timestamp="1774814915715"/>
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.timelock{TimeLockController.java[TimeLockController" modifiers="1" timestamp="1774900567883"/>
|
||||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.common{BaseLockService.java[BaseLockService" modifiers="1025" timestamp="1774814915716"/>
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.common{BaseLockService.java[BaseLockService" modifiers="1025" timestamp="1774814915716"/>
|
||||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.ttlock{TTLockCallback.java[TTLockCallback" modifiers="1" timestamp="1774814915716"/>
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.ttlock{TTLockCallback.java[TTLockCallback" modifiers="1" timestamp="1774814915716"/>
|
||||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.lockcontroll{TTLockControl.java[TTLockControl" modifiers="1" timestamp="1774814915717"/>
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.lockcontroll{TTLockControl.java[TTLockControl" modifiers="1" timestamp="1774814915717"/>
|
||||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.cardlock{CardLockController.java[CardLockController" modifiers="1" timestamp="1774814915718"/>
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.cardlock{CardLockController.java[CardLockController" modifiers="1" timestamp="1774903113531"/>
|
||||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.common{BaseLockEntity.java[BaseLockEntity" modifiers="1" timestamp="1774814915716"/>
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.common{BaseLockEntity.java[BaseLockEntity" modifiers="1" timestamp="1774814915716"/>
|
||||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.cardlock{CardLockService.java[CardLockService" modifiers="1" timestamp="1774814915718"/>
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.cardlock{CardLockService.java[CardLockService" modifiers="1" timestamp="1774814915718"/>
|
||||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.timelock{TimeLockService.java[TimeLockService" modifiers="1" timestamp="1774814915716"/>
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.timelock{TimeLockService.java[TimeLockService" modifiers="1" timestamp="1774814915716"/>
|
||||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.common.aufgaben{Aufgabe.java[Aufgabe" modifiers="1" timestamp="1774814915721"/>
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.common.aufgaben{Aufgabe.java[Aufgabe" modifiers="1" timestamp="1774814915721"/>
|
||||||
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.common.aufgaben{DefaultFiller.java[DefaultFiller" modifiers="1" timestamp="1774814915721"/>
|
||||||
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.mail{MailService.java[MailService" modifiers="1" timestamp="1774814915713"/>
|
||||||
</typeInfoHistroy>
|
</typeInfoHistroy>
|
||||||
|
|||||||
@@ -34,4 +34,5 @@
|
|||||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.unlock.TempOpeningReason"/>
|
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.unlock.TempOpeningReason"/>
|
||||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.vanilla.VanillaMitspieler"/>
|
<fullyQualifiedTypeName name="de.oaa.xxx.games.vanilla.VanillaMitspieler"/>
|
||||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.common.aufgaben.CommonMitspieler"/>
|
<fullyQualifiedTypeName name="de.oaa.xxx.games.common.aufgaben.CommonMitspieler"/>
|
||||||
|
<fullyQualifiedTypeName name="java.util.Comparator"/>
|
||||||
</qualifiedTypeNameHistroy>
|
</qualifiedTypeNameHistroy>
|
||||||
|
|||||||
@@ -16,3 +16,6 @@
|
|||||||
2026-03-27 07:46:24,300 [Worker-7: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
2026-03-27 07:46:24,300 [Worker-7: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
||||||
2026-03-29 16:28:13,219 [Worker-2: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is out-of-date. Trying to update.
|
2026-03-29 16:28:13,219 [Worker-2: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is out-of-date. Trying to update.
|
||||||
2026-03-30 07:33:05,316 [Worker-5: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
2026-03-30 07:33:05,316 [Worker-5: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
||||||
|
2026-03-31 08:24:38,073 [Worker-1: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is out-of-date. Trying to update.
|
||||||
|
2026-03-31 11:33:17,509 [Worker-8: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
||||||
|
2026-03-31 20:02:56,538 [Worker-2: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#Mon Mar 30 07:33:01 CEST 2026
|
#Tue Mar 31 20:02:52 CEST 2026
|
||||||
org.eclipse.core.runtime=2
|
org.eclipse.core.runtime=2
|
||||||
org.eclipse.platform=4.39.0.v20260226-0420
|
org.eclipse.platform=4.39.0.v20260226-0420
|
||||||
|
|||||||
BIN
bilder/Gemini_Generated_Image_xdceypxdceypxdce.png
Normal file
|
After Width: | Height: | Size: 302 KiB |
BIN
bilder/dunno.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
bilder/negative.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
bilder/neutral.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
bilder/positiv.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
bilder/verynegative.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
bilder/verypositiv.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
21
xxxthegame/deploy.sh
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Konfiguration
|
||||||
|
REMOTE_CONTEXT="proxmox-remote"
|
||||||
|
IMAGE_NAME="xxx-sphere"
|
||||||
|
TAG="latest"
|
||||||
|
|
||||||
|
echo "--- 1. Gradle Build: Erstelle Docker Image lokal ---"
|
||||||
|
# Dieser Befehl baut die Jar UND das Docker Image direkt in deinem lokalen Docker
|
||||||
|
./gradlew bootBuildImage --imageName=$IMAGE_NAME:$TAG
|
||||||
|
|
||||||
|
echo "--- 2. Transfer: Image zum Proxmox-Server schieben ---"
|
||||||
|
# Wir 'pipen' das Image direkt über SSH auf den Zielserver
|
||||||
|
docker save $IMAGE_NAME:$TAG | docker --context $REMOTE_CONTEXT load
|
||||||
|
|
||||||
|
echo "--- 3. Remote Deployment: Starten auf Proxmox ---"
|
||||||
|
# Wir führen Docker Compose direkt im Remote-Kontext aus
|
||||||
|
# --force-recreate stellt sicher, dass die App mit dem neuen Image neu startet
|
||||||
|
docker --context $REMOTE_CONTEXT compose up -d --force-recreate
|
||||||
|
|
||||||
|
echo "--- Fertig! Die App läuft auf dem Proxmox-Server ---"
|
||||||
32
xxxthegame/docker-compose.yml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: mysql-db
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
MYSQL_DATABASE: xxx_sphere
|
||||||
|
MYSQL_ROOT_PASSWORD: xxxsphere123!
|
||||||
|
ports:
|
||||||
|
- "3306:3306" # <--- Jetzt steht es korrekt alleine!
|
||||||
|
volumes:
|
||||||
|
# Format: [Pfad auf dem Proxmox-Host]:[Pfad im Container]
|
||||||
|
- /mnt/pve_nas/.mysql_data:/var/lib/mysql
|
||||||
|
|
||||||
|
app:
|
||||||
|
image: xxx-sphere:latest
|
||||||
|
container_name: spring-boot-app
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
environment:
|
||||||
|
# Wir biegen localhost auf den Service-Namen 'db' um
|
||||||
|
- SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/xxx_sphere?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
|
||||||
|
# Hier injizieren wir die Werte für deine Platzhalter
|
||||||
|
- DB_USER=root
|
||||||
|
- DB_PASSWORD=xxxsphere123!
|
||||||
|
# Wartet kurz, bis die DB wirklich bereit ist (optional, aber empfohlen)
|
||||||
|
restart: on-failure
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql_data:
|
||||||
@@ -238,7 +238,7 @@ public class VanillaGameController {
|
|||||||
return ResponseEntity.badRequest().build();
|
return ResponseEntity.badRequest().build();
|
||||||
}
|
}
|
||||||
// Max 2 Mitspieler (1 Host + max 1 Gast)
|
// Max 2 Mitspieler (1 Host + max 1 Gast)
|
||||||
if (session.getMitspieler().size() >= 1) {
|
if (session.getMitspieler().size() >= 2) {
|
||||||
return ResponseEntity.status(409).build();
|
return ResponseEntity.status(409).build();
|
||||||
}
|
}
|
||||||
VanillaMitspielerEntity entity = new VanillaMitspielerEntity();
|
VanillaMitspielerEntity entity = new VanillaMitspielerEntity();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import de.oaa.xxx.games.common.aufgaben.Toy;
|
|||||||
import de.oaa.xxx.games.common.aufgaben.ToyPage;
|
import de.oaa.xxx.games.common.aufgaben.ToyPage;
|
||||||
import de.oaa.xxx.games.common.entity.AufgabenGruppeEntity;
|
import de.oaa.xxx.games.common.entity.AufgabenGruppeEntity;
|
||||||
import de.oaa.xxx.games.common.entity.ToyEntity;
|
import de.oaa.xxx.games.common.entity.ToyEntity;
|
||||||
|
import de.oaa.xxx.games.common.repository.AufgabenGruppeRepository;
|
||||||
import de.oaa.xxx.games.common.repository.GruppenAboRepository;
|
import de.oaa.xxx.games.common.repository.GruppenAboRepository;
|
||||||
import de.oaa.xxx.games.common.repository.ToyRepository;
|
import de.oaa.xxx.games.common.repository.ToyRepository;
|
||||||
import de.oaa.xxx.subscription.SubscriptionLimitService;
|
import de.oaa.xxx.subscription.SubscriptionLimitService;
|
||||||
@@ -33,6 +34,7 @@ import java.util.Base64;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -47,15 +49,18 @@ public class VanillaToyController {
|
|||||||
private final ToyRepository toyRepository;
|
private final ToyRepository toyRepository;
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
private final GruppenAboRepository aboRepository;
|
private final GruppenAboRepository aboRepository;
|
||||||
|
private final AufgabenGruppeRepository gruppeRepository;
|
||||||
private final SubscriptionLimitService limitService;
|
private final SubscriptionLimitService limitService;
|
||||||
|
|
||||||
public VanillaToyController(ToyRepository toyRepository,
|
public VanillaToyController(ToyRepository toyRepository,
|
||||||
UserService userService,
|
UserService userService,
|
||||||
GruppenAboRepository aboRepository,
|
GruppenAboRepository aboRepository,
|
||||||
|
AufgabenGruppeRepository gruppeRepository,
|
||||||
SubscriptionLimitService limitService) {
|
SubscriptionLimitService limitService) {
|
||||||
this.toyRepository = toyRepository;
|
this.toyRepository = toyRepository;
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
this.aboRepository = aboRepository;
|
this.aboRepository = aboRepository;
|
||||||
|
this.gruppeRepository = gruppeRepository;
|
||||||
this.limitService = limitService;
|
this.limitService = limitService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +119,30 @@ public class VanillaToyController {
|
|||||||
return ResponseEntity.ok(result);
|
return ResponseEntity.ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all distinct toys required by aufgaben and finisher of the given gruppe IDs.
|
||||||
|
* Only vanilla-safe groups (no Strafen, no Sperren) are considered.
|
||||||
|
*/
|
||||||
|
@GetMapping("/required")
|
||||||
|
public ResponseEntity<List<Toy>> required(@RequestParam List<UUID> gruppenIds) {
|
||||||
|
Map<UUID, ToyEntity> toyMap = new java.util.LinkedHashMap<>();
|
||||||
|
gruppeRepository.findAllById(gruppenIds).forEach(gruppe -> {
|
||||||
|
gruppe.getAufgaben().forEach(a -> {
|
||||||
|
if (a.getBenoetigteToys() != null)
|
||||||
|
a.getBenoetigteToys().forEach(t -> toyMap.putIfAbsent(t.getToyId(), t));
|
||||||
|
});
|
||||||
|
gruppe.getFinisher().forEach(f -> {
|
||||||
|
if (f.getBenoetigteToys() != null)
|
||||||
|
f.getBenoetigteToys().forEach(t -> toyMap.putIfAbsent(t.getToyId(), t));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
List<Toy> result = toyMap.values().stream()
|
||||||
|
.sorted(Comparator.comparing(ToyEntity::getName, String.CASE_INSENSITIVE_ORDER))
|
||||||
|
.map(ToyEntity::toToy)
|
||||||
|
.toList();
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/{toyId}")
|
@GetMapping("/{toyId}")
|
||||||
public ResponseEntity<Toy> get(@PathVariable UUID toyId) {
|
public ResponseEntity<Toy> get(@PathVariable UUID toyId) {
|
||||||
return toyRepository.findById(toyId)
|
return toyRepository.findById(toyId)
|
||||||
|
|||||||
@@ -12,26 +12,28 @@ import org.springframework.stereotype.Service;
|
|||||||
@Service
|
@Service
|
||||||
public class MailService {
|
public class MailService {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(MailService.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(MailService.class);
|
||||||
|
|
||||||
private final JavaMailSender mailSender;
|
private final JavaMailSender mailSender;
|
||||||
|
|
||||||
public MailService(JavaMailSender mailSender) {
|
public MailService(JavaMailSender mailSender) {
|
||||||
this.mailSender = mailSender;
|
this.mailSender = mailSender;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean send(Email email) {
|
public boolean send(Email email) {
|
||||||
try {
|
try {
|
||||||
MimeMessage message = mailSender.createMimeMessage();
|
MimeMessage message = mailSender.createMimeMessage();
|
||||||
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(email.getEmailAdresse()));
|
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(email.getEmailAdresse()));
|
||||||
message.setSubject(email.getTitel());
|
message.setSubject(email.getTitel());
|
||||||
message.setFrom(InternetAddress.parse("noreply@xxx-bdsmgame.de")[0]);
|
message.setFrom(InternetAddress.parse("noreply@xxx-sphere.de")[0]);
|
||||||
message.setContent(email.getText(), "text/html; charset=utf-8");
|
message.setContent(email.getText(), "text/html; charset=utf-8");
|
||||||
mailSender.send(message);
|
message.addHeader("X-Mailin-Tag", "no-tracking");
|
||||||
return true;
|
message.addHeader("X-Sib-Attributes", "{\"X-SIB-TRACKING\":\"0\"}");
|
||||||
} catch (MessagingException e) {
|
mailSender.send(message);
|
||||||
LOGGER.error(e.getLocalizedMessage(), e);
|
return true;
|
||||||
return false;
|
} catch (MessagingException e) {
|
||||||
}
|
LOGGER.error(e.getLocalizedMessage(), e);
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -347,6 +347,7 @@ public class SocialController {
|
|||||||
user.getSichtbarkeitPinnwand(),
|
user.getSichtbarkeitPinnwand(),
|
||||||
user.getSichtbarkeitXp(),
|
user.getSichtbarkeitXp(),
|
||||||
user.getSichtbarkeitLockhistorie(),
|
user.getSichtbarkeitLockhistorie(),
|
||||||
|
user.getSichtbarkeitVorlieben(),
|
||||||
user.isProfilBeiVeroeffentlichungenSichtbar());
|
user.isProfilBeiVeroeffentlichungenSichtbar());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,12 +31,13 @@ public record UserProfile(
|
|||||||
Sichtbarkeit sichtbarkeitPinnwand,
|
Sichtbarkeit sichtbarkeitPinnwand,
|
||||||
Sichtbarkeit sichtbarkeitXp,
|
Sichtbarkeit sichtbarkeitXp,
|
||||||
Sichtbarkeit sichtbarkeitLockhistorie,
|
Sichtbarkeit sichtbarkeitLockhistorie,
|
||||||
|
Sichtbarkeit sichtbarkeitVorlieben,
|
||||||
boolean profilBeiVeroeffentlichungenSichtbar
|
boolean profilBeiVeroeffentlichungenSichtbar
|
||||||
) {
|
) {
|
||||||
/** Compact constructor for contexts where profile details are not needed (friend list etc.) */
|
/** Compact constructor for contexts where profile details are not needed (friend list etc.) */
|
||||||
public UserProfile(UUID userId, String name, String profilePicture, String profilePictureHq, String friendStatus) {
|
public UserProfile(UUID userId, String name, String profilePicture, String profilePictureHq, String friendStatus) {
|
||||||
this(userId, name, profilePicture, profilePictureHq, friendStatus,
|
this(userId, name, profilePicture, profilePictureHq, friendStatus,
|
||||||
null, null, null, null, null, null, null, 0, 0, 0,
|
null, null, null, null, null, null, null, 0, 0, 0,
|
||||||
null, null, null, null, null, null, null, false);
|
null, null, null, null, null, null, null, null, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ public class UserController {
|
|||||||
Sichtbarkeit sichtbarkeitPinnwand,
|
Sichtbarkeit sichtbarkeitPinnwand,
|
||||||
Sichtbarkeit sichtbarkeitXp,
|
Sichtbarkeit sichtbarkeitXp,
|
||||||
Sichtbarkeit sichtbarkeitLockhistorie,
|
Sichtbarkeit sichtbarkeitLockhistorie,
|
||||||
|
Sichtbarkeit sichtbarkeitVorlieben,
|
||||||
Boolean profilBeiVeroeffentlichungenSichtbar) {}
|
Boolean profilBeiVeroeffentlichungenSichtbar) {}
|
||||||
|
|
||||||
@PutMapping("/me/picture")
|
@PutMapping("/me/picture")
|
||||||
@@ -138,6 +139,7 @@ public class UserController {
|
|||||||
if (request.sichtbarkeitPinnwand() != null) user.setSichtbarkeitPinnwand(request.sichtbarkeitPinnwand());
|
if (request.sichtbarkeitPinnwand() != null) user.setSichtbarkeitPinnwand(request.sichtbarkeitPinnwand());
|
||||||
if (request.sichtbarkeitXp() != null) user.setSichtbarkeitXp(request.sichtbarkeitXp());
|
if (request.sichtbarkeitXp() != null) user.setSichtbarkeitXp(request.sichtbarkeitXp());
|
||||||
if (request.sichtbarkeitLockhistorie()!= null) user.setSichtbarkeitLockhistorie(request.sichtbarkeitLockhistorie());
|
if (request.sichtbarkeitLockhistorie()!= null) user.setSichtbarkeitLockhistorie(request.sichtbarkeitLockhistorie());
|
||||||
|
if (request.sichtbarkeitVorlieben() != null) user.setSichtbarkeitVorlieben(request.sichtbarkeitVorlieben());
|
||||||
if (request.profilBeiVeroeffentlichungenSichtbar() != null) {
|
if (request.profilBeiVeroeffentlichungenSichtbar() != null) {
|
||||||
boolean showAuthor = request.profilBeiVeroeffentlichungenSichtbar();
|
boolean showAuthor = request.profilBeiVeroeffentlichungenSichtbar();
|
||||||
user.setProfilBeiVeroeffentlichungenSichtbar(showAuthor);
|
user.setProfilBeiVeroeffentlichungenSichtbar(showAuthor);
|
||||||
|
|||||||
@@ -92,6 +92,10 @@ public class UserEntity {
|
|||||||
@Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'")
|
@Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'")
|
||||||
private Sichtbarkeit sichtbarkeitLockhistorie = Sichtbarkeit.ALLE;
|
private Sichtbarkeit sichtbarkeitLockhistorie = Sichtbarkeit.ALLE;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'")
|
||||||
|
private Sichtbarkeit sichtbarkeitVorlieben = Sichtbarkeit.ALLE;
|
||||||
|
|
||||||
@Column(nullable = false, columnDefinition = "TINYINT(1) DEFAULT 0")
|
@Column(nullable = false, columnDefinition = "TINYINT(1) DEFAULT 0")
|
||||||
private boolean profilBeiVeroeffentlichungenSichtbar = false;
|
private boolean profilBeiVeroeffentlichungenSichtbar = false;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package de.oaa.xxx.vorlieben;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "user_vorliebe",
|
||||||
|
uniqueConstraints = @UniqueConstraint(columnNames = {"userId", "itemId"}))
|
||||||
|
public class UserVorliebeEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private UUID userId;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private UUID itemId;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(nullable = false, length = 30)
|
||||||
|
private VorliebeBewertung bewertung;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package de.oaa.xxx.vorlieben;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface UserVorliebeRepository extends JpaRepository<UserVorliebeEntity, UUID> {
|
||||||
|
List<UserVorliebeEntity> findByUserId(UUID userId);
|
||||||
|
Optional<UserVorliebeEntity> findByUserIdAndItemId(UUID userId, UUID itemId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package de.oaa.xxx.vorlieben;
|
||||||
|
|
||||||
|
public enum VorliebeBewertung {
|
||||||
|
GEHT_GAR_NICHT("Geht gar nicht"),
|
||||||
|
EHER_NICHT("Eher nicht"),
|
||||||
|
NEUTRAL("Neutral"),
|
||||||
|
MAG_ICH("Mag ich"),
|
||||||
|
UNBEDINGT("Unbedingt"),
|
||||||
|
WILL_AUSPROBIEREN("Will ich ausprobieren");
|
||||||
|
|
||||||
|
private final String label;
|
||||||
|
|
||||||
|
VorliebeBewertung(String label) { this.label = label; }
|
||||||
|
|
||||||
|
public String getLabel() { return label; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package de.oaa.xxx.vorlieben;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "vorliebe_item")
|
||||||
|
public class VorliebeItemEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column
|
||||||
|
private UUID itemId;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private UUID kategorieId;
|
||||||
|
|
||||||
|
@Column(nullable = false, length = 200)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(nullable = false, columnDefinition = "INT DEFAULT 0")
|
||||||
|
private int sortOrder;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package de.oaa.xxx.vorlieben;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface VorliebeItemRepository extends JpaRepository<VorliebeItemEntity, UUID> {
|
||||||
|
List<VorliebeItemEntity> findAllByOrderBySortOrderAscNameAsc();
|
||||||
|
boolean existsByKategorieId(UUID kategorieId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package de.oaa.xxx.vorlieben;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "vorliebe_kategorie")
|
||||||
|
public class VorliebeKategorieEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column
|
||||||
|
private UUID kategorieId;
|
||||||
|
|
||||||
|
@Column(nullable = false, length = 100)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(nullable = false, columnDefinition = "INT DEFAULT 0")
|
||||||
|
private int sortOrder;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package de.oaa.xxx.vorlieben;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface VorliebeKategorieRepository extends JpaRepository<VorliebeKategorieEntity, UUID> {
|
||||||
|
List<VorliebeKategorieEntity> findAllByOrderBySortOrderAscNameAsc();
|
||||||
|
}
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
package de.oaa.xxx.vorlieben;
|
||||||
|
|
||||||
|
import de.oaa.xxx.admin.AdminEntity;
|
||||||
|
import de.oaa.xxx.admin.AdminRepository;
|
||||||
|
import de.oaa.xxx.user.UserService;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/admin/vorlieben")
|
||||||
|
@Transactional
|
||||||
|
public class VorliebenAdminController {
|
||||||
|
|
||||||
|
private final VorliebeKategorieRepository kategorieRepository;
|
||||||
|
private final VorliebeItemRepository itemRepository;
|
||||||
|
private final UserVorliebeRepository userVorliebeRepository;
|
||||||
|
private final AdminRepository adminRepository;
|
||||||
|
private final UserService userService;
|
||||||
|
|
||||||
|
public VorliebenAdminController(VorliebeKategorieRepository kategorieRepository,
|
||||||
|
VorliebeItemRepository itemRepository,
|
||||||
|
UserVorliebeRepository userVorliebeRepository,
|
||||||
|
AdminRepository adminRepository,
|
||||||
|
UserService userService) {
|
||||||
|
this.kategorieRepository = kategorieRepository;
|
||||||
|
this.itemRepository = itemRepository;
|
||||||
|
this.userVorliebeRepository = userVorliebeRepository;
|
||||||
|
this.adminRepository = adminRepository;
|
||||||
|
this.userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AdminEntity requireAdmin(Principal principal) {
|
||||||
|
var user = userService.requireUser(principal);
|
||||||
|
return adminRepository.findByUserId(user.getUserId())
|
||||||
|
.orElseThrow(() -> new org.springframework.web.server.ResponseStatusException(
|
||||||
|
org.springframework.http.HttpStatus.FORBIDDEN, "Kein Admin"));
|
||||||
|
}
|
||||||
|
|
||||||
|
record KategorieRequest(String name, int sortOrder) {}
|
||||||
|
record ItemRequest(UUID kategorieId, String name, int sortOrder) {}
|
||||||
|
record KategorieDto(UUID kategorieId, String name, int sortOrder) {}
|
||||||
|
record ItemDto(UUID itemId, UUID kategorieId, String name, int sortOrder) {}
|
||||||
|
|
||||||
|
// ── Kategorien ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@GetMapping("/kategorien")
|
||||||
|
public ResponseEntity<List<KategorieDto>> getKategorien(Principal principal) {
|
||||||
|
requireAdmin(principal);
|
||||||
|
List<KategorieDto> result = kategorieRepository.findAllByOrderBySortOrderAscNameAsc().stream()
|
||||||
|
.map(k -> new KategorieDto(k.getKategorieId(), k.getName(), k.getSortOrder()))
|
||||||
|
.toList();
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/kategorien")
|
||||||
|
public ResponseEntity<KategorieDto> createKategorie(@RequestBody KategorieRequest req, Principal principal) {
|
||||||
|
requireAdmin(principal);
|
||||||
|
if (req.name() == null || req.name().isBlank()) return ResponseEntity.badRequest().build();
|
||||||
|
VorliebeKategorieEntity entity = new VorliebeKategorieEntity();
|
||||||
|
entity.setKategorieId(UUID.randomUUID());
|
||||||
|
entity.setName(req.name().trim());
|
||||||
|
entity.setSortOrder(req.sortOrder());
|
||||||
|
kategorieRepository.save(entity);
|
||||||
|
return ResponseEntity.status(201).body(
|
||||||
|
new KategorieDto(entity.getKategorieId(), entity.getName(), entity.getSortOrder()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/kategorien/{id}")
|
||||||
|
public ResponseEntity<Void> updateKategorie(@PathVariable UUID id,
|
||||||
|
@RequestBody KategorieRequest req,
|
||||||
|
Principal principal) {
|
||||||
|
requireAdmin(principal);
|
||||||
|
if (req.name() == null || req.name().isBlank()) return ResponseEntity.badRequest().build();
|
||||||
|
VorliebeKategorieEntity entity = kategorieRepository.findById(id).orElse(null);
|
||||||
|
if (entity == null) return ResponseEntity.notFound().build();
|
||||||
|
entity.setName(req.name().trim());
|
||||||
|
entity.setSortOrder(req.sortOrder());
|
||||||
|
kategorieRepository.save(entity);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/kategorien/{id}")
|
||||||
|
public ResponseEntity<Void> deleteKategorie(@PathVariable UUID id, Principal principal) {
|
||||||
|
requireAdmin(principal);
|
||||||
|
if (!kategorieRepository.existsById(id)) return ResponseEntity.notFound().build();
|
||||||
|
if (itemRepository.existsByKategorieId(id)) {
|
||||||
|
return ResponseEntity.status(409)
|
||||||
|
.header("X-Error", "has-items")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
kategorieRepository.deleteById(id);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Items ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@GetMapping("/items")
|
||||||
|
public ResponseEntity<List<ItemDto>> getItems(Principal principal) {
|
||||||
|
requireAdmin(principal);
|
||||||
|
List<ItemDto> result = itemRepository.findAllByOrderBySortOrderAscNameAsc().stream()
|
||||||
|
.map(i -> new ItemDto(i.getItemId(), i.getKategorieId(), i.getName(), i.getSortOrder()))
|
||||||
|
.toList();
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/items")
|
||||||
|
public ResponseEntity<ItemDto> createItem(@RequestBody ItemRequest req, Principal principal) {
|
||||||
|
requireAdmin(principal);
|
||||||
|
if (req.name() == null || req.name().isBlank() || req.kategorieId() == null)
|
||||||
|
return ResponseEntity.badRequest().build();
|
||||||
|
if (!kategorieRepository.existsById(req.kategorieId()))
|
||||||
|
return ResponseEntity.status(422).header("X-Error", "kategorie-not-found").build();
|
||||||
|
VorliebeItemEntity entity = new VorliebeItemEntity();
|
||||||
|
entity.setItemId(UUID.randomUUID());
|
||||||
|
entity.setKategorieId(req.kategorieId());
|
||||||
|
entity.setName(req.name().trim());
|
||||||
|
entity.setSortOrder(req.sortOrder());
|
||||||
|
itemRepository.save(entity);
|
||||||
|
return ResponseEntity.status(201).body(
|
||||||
|
new ItemDto(entity.getItemId(), entity.getKategorieId(), entity.getName(), entity.getSortOrder()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/items/{id}")
|
||||||
|
public ResponseEntity<Void> updateItem(@PathVariable UUID id,
|
||||||
|
@RequestBody ItemRequest req,
|
||||||
|
Principal principal) {
|
||||||
|
requireAdmin(principal);
|
||||||
|
if (req.name() == null || req.name().isBlank()) return ResponseEntity.badRequest().build();
|
||||||
|
VorliebeItemEntity entity = itemRepository.findById(id).orElse(null);
|
||||||
|
if (entity == null) return ResponseEntity.notFound().build();
|
||||||
|
if (req.kategorieId() != null && !kategorieRepository.existsById(req.kategorieId()))
|
||||||
|
return ResponseEntity.status(422).header("X-Error", "kategorie-not-found").build();
|
||||||
|
if (req.kategorieId() != null) entity.setKategorieId(req.kategorieId());
|
||||||
|
entity.setName(req.name().trim());
|
||||||
|
entity.setSortOrder(req.sortOrder());
|
||||||
|
itemRepository.save(entity);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/items/{id}")
|
||||||
|
public ResponseEntity<Void> deleteItem(@PathVariable UUID id, Principal principal) {
|
||||||
|
requireAdmin(principal);
|
||||||
|
VorliebeItemEntity entity = itemRepository.findById(id).orElse(null);
|
||||||
|
if (entity == null) return ResponseEntity.notFound().build();
|
||||||
|
// Remove all user ratings for this item first
|
||||||
|
List<UserVorliebeEntity> ratings = userVorliebeRepository.findAll().stream()
|
||||||
|
.filter(uv -> uv.getItemId().equals(id)).toList();
|
||||||
|
userVorliebeRepository.deleteAll(ratings);
|
||||||
|
itemRepository.delete(entity);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Export ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
record ExportKategorie(UUID kategorieId, String name, int sortOrder, List<ExportItem> items) {}
|
||||||
|
record ExportItem(UUID itemId, String name, int sortOrder) {}
|
||||||
|
|
||||||
|
@GetMapping("/export")
|
||||||
|
public ResponseEntity<List<ExportKategorie>> export(Principal principal) {
|
||||||
|
requireAdmin(principal);
|
||||||
|
List<VorliebeKategorieEntity> kategorien = kategorieRepository.findAllByOrderBySortOrderAscNameAsc();
|
||||||
|
List<VorliebeItemEntity> allItems = itemRepository.findAllByOrderBySortOrderAscNameAsc();
|
||||||
|
Map<UUID, List<VorliebeItemEntity>> byKat = allItems.stream()
|
||||||
|
.collect(Collectors.groupingBy(VorliebeItemEntity::getKategorieId));
|
||||||
|
|
||||||
|
List<ExportKategorie> result = kategorien.stream()
|
||||||
|
.map(k -> new ExportKategorie(
|
||||||
|
k.getKategorieId(), k.getName(), k.getSortOrder(),
|
||||||
|
byKat.getOrDefault(k.getKategorieId(), List.of()).stream()
|
||||||
|
.map(i -> new ExportItem(i.getItemId(), i.getName(), i.getSortOrder()))
|
||||||
|
.toList()))
|
||||||
|
.toList();
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.header("Content-Disposition", "attachment; filename=\"vorlieben-export.json\"")
|
||||||
|
.body(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Import ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
record ImportItem(String name, int sortOrder) {}
|
||||||
|
record ImportKategorie(String name, int sortOrder, List<ImportItem> items) {}
|
||||||
|
record ImportResult(int kategorienCreated, int kategorienSkipped, int itemsCreated, int itemsSkipped) {}
|
||||||
|
|
||||||
|
@PostMapping("/import")
|
||||||
|
public ResponseEntity<ImportResult> importData(@RequestBody List<ImportKategorie> data,
|
||||||
|
Principal principal) {
|
||||||
|
requireAdmin(principal);
|
||||||
|
if (data == null) return ResponseEntity.badRequest().build();
|
||||||
|
|
||||||
|
int kCreated = 0, kSkipped = 0, iCreated = 0, iSkipped = 0;
|
||||||
|
List<VorliebeKategorieEntity> existingKategorien = kategorieRepository.findAllByOrderBySortOrderAscNameAsc();
|
||||||
|
Map<String, VorliebeKategorieEntity> byName = existingKategorien.stream()
|
||||||
|
.collect(Collectors.toMap(k -> k.getName().toLowerCase(), k -> k,
|
||||||
|
(a, b) -> a));
|
||||||
|
|
||||||
|
for (ImportKategorie ik : data) {
|
||||||
|
if (ik.name() == null || ik.name().isBlank()) { kSkipped++; continue; }
|
||||||
|
VorliebeKategorieEntity kat = byName.get(ik.name().trim().toLowerCase());
|
||||||
|
if (kat == null) {
|
||||||
|
kat = new VorliebeKategorieEntity();
|
||||||
|
kat.setKategorieId(UUID.randomUUID());
|
||||||
|
kat.setName(ik.name().trim());
|
||||||
|
kat.setSortOrder(ik.sortOrder());
|
||||||
|
kategorieRepository.save(kat);
|
||||||
|
byName.put(kat.getName().toLowerCase(), kat);
|
||||||
|
kCreated++;
|
||||||
|
} else {
|
||||||
|
kSkipped++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ik.items() == null) continue;
|
||||||
|
final UUID katId = kat.getKategorieId();
|
||||||
|
List<VorliebeItemEntity> existingItems =
|
||||||
|
itemRepository.findAllByOrderBySortOrderAscNameAsc().stream()
|
||||||
|
.filter(i -> i.getKategorieId().equals(katId))
|
||||||
|
.toList();
|
||||||
|
Set<String> existingNames = existingItems.stream()
|
||||||
|
.map(i -> i.getName().toLowerCase()).collect(Collectors.toSet());
|
||||||
|
|
||||||
|
for (ImportItem ii : ik.items()) {
|
||||||
|
if (ii.name() == null || ii.name().isBlank()) { iSkipped++; continue; }
|
||||||
|
if (existingNames.contains(ii.name().trim().toLowerCase())) { iSkipped++; continue; }
|
||||||
|
VorliebeItemEntity item = new VorliebeItemEntity();
|
||||||
|
item.setItemId(UUID.randomUUID());
|
||||||
|
item.setKategorieId(katId);
|
||||||
|
item.setName(ii.name().trim());
|
||||||
|
item.setSortOrder(ii.sortOrder());
|
||||||
|
itemRepository.save(item);
|
||||||
|
iCreated++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(new ImportResult(kCreated, kSkipped, iCreated, iSkipped));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
package de.oaa.xxx.vorlieben;
|
||||||
|
|
||||||
|
import de.oaa.xxx.social.entity.FriendshipEntity;
|
||||||
|
import de.oaa.xxx.social.repository.FriendshipRepository;
|
||||||
|
import de.oaa.xxx.user.Sichtbarkeit;
|
||||||
|
import de.oaa.xxx.user.UserEntity;
|
||||||
|
import de.oaa.xxx.user.UserRepository;
|
||||||
|
import de.oaa.xxx.user.UserService;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/vorlieben")
|
||||||
|
@Transactional
|
||||||
|
public class VorliebenController {
|
||||||
|
|
||||||
|
private final VorliebeKategorieRepository kategorieRepository;
|
||||||
|
private final VorliebeItemRepository itemRepository;
|
||||||
|
private final UserVorliebeRepository userVorliebeRepository;
|
||||||
|
private final UserService userService;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final FriendshipRepository friendshipRepository;
|
||||||
|
|
||||||
|
public VorliebenController(VorliebeKategorieRepository kategorieRepository,
|
||||||
|
VorliebeItemRepository itemRepository,
|
||||||
|
UserVorliebeRepository userVorliebeRepository,
|
||||||
|
UserService userService,
|
||||||
|
UserRepository userRepository,
|
||||||
|
FriendshipRepository friendshipRepository) {
|
||||||
|
this.kategorieRepository = kategorieRepository;
|
||||||
|
this.itemRepository = itemRepository;
|
||||||
|
this.userVorliebeRepository = userVorliebeRepository;
|
||||||
|
this.userService = userService;
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.friendshipRepository = friendshipRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
record ItemDto(UUID itemId, String name, int sortOrder) {}
|
||||||
|
record KategorieWithItems(UUID kategorieId, String name, int sortOrder, List<ItemDto> items) {}
|
||||||
|
|
||||||
|
/** Returns all categories with their items – used by the profile edit page. */
|
||||||
|
@GetMapping("/items")
|
||||||
|
public ResponseEntity<List<KategorieWithItems>> getItems() {
|
||||||
|
List<VorliebeKategorieEntity> kategorien = kategorieRepository.findAllByOrderBySortOrderAscNameAsc();
|
||||||
|
List<VorliebeItemEntity> allItems = itemRepository.findAllByOrderBySortOrderAscNameAsc();
|
||||||
|
|
||||||
|
Map<UUID, List<VorliebeItemEntity>> byKategorie = allItems.stream()
|
||||||
|
.collect(Collectors.groupingBy(VorliebeItemEntity::getKategorieId));
|
||||||
|
|
||||||
|
List<KategorieWithItems> result = kategorien.stream()
|
||||||
|
.map(k -> new KategorieWithItems(
|
||||||
|
k.getKategorieId(), k.getName(), k.getSortOrder(),
|
||||||
|
byKategorie.getOrDefault(k.getKategorieId(), List.of()).stream()
|
||||||
|
.map(i -> new ItemDto(i.getItemId(), i.getName(), i.getSortOrder()))
|
||||||
|
.toList()))
|
||||||
|
.filter(k -> !k.items().isEmpty())
|
||||||
|
.toList();
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the current user's ratings as a map of itemId → bewertung name. */
|
||||||
|
@GetMapping("/me")
|
||||||
|
public ResponseEntity<Map<String, String>> getMyVorlieben(Principal principal) {
|
||||||
|
UUID userId = userService.requireUser(principal).getUserId();
|
||||||
|
Map<String, String> result = userVorliebeRepository.findByUserId(userId).stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
uv -> uv.getItemId().toString(),
|
||||||
|
uv -> uv.getBewertung().name()));
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Saves the current user's ratings. Value null or blank removes the rating. */
|
||||||
|
@PutMapping("/me")
|
||||||
|
public ResponseEntity<Void> saveMyVorlieben(@RequestBody Map<String, String> ratings, Principal principal) {
|
||||||
|
UUID userId = userService.requireUser(principal).getUserId();
|
||||||
|
for (var entry : ratings.entrySet()) {
|
||||||
|
UUID itemId;
|
||||||
|
try { itemId = UUID.fromString(entry.getKey()); }
|
||||||
|
catch (IllegalArgumentException e) { continue; }
|
||||||
|
|
||||||
|
String bewertungStr = entry.getValue();
|
||||||
|
if (bewertungStr == null || bewertungStr.isBlank()) {
|
||||||
|
userVorliebeRepository.findByUserIdAndItemId(userId, itemId)
|
||||||
|
.ifPresent(userVorliebeRepository::delete);
|
||||||
|
} else {
|
||||||
|
VorliebeBewertung bewertung;
|
||||||
|
try { bewertung = VorliebeBewertung.valueOf(bewertungStr); }
|
||||||
|
catch (IllegalArgumentException e) { continue; }
|
||||||
|
|
||||||
|
UserVorliebeEntity uv = userVorliebeRepository.findByUserIdAndItemId(userId, itemId)
|
||||||
|
.orElseGet(() -> {
|
||||||
|
UserVorliebeEntity n = new UserVorliebeEntity();
|
||||||
|
n.setId(UUID.randomUUID());
|
||||||
|
n.setUserId(userId);
|
||||||
|
n.setItemId(itemId);
|
||||||
|
return n;
|
||||||
|
});
|
||||||
|
uv.setBewertung(bewertung);
|
||||||
|
userVorliebeRepository.save(uv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns another user's ratings, respecting their privacy setting. */
|
||||||
|
@GetMapping("/user/{userId}")
|
||||||
|
public ResponseEntity<Map<String, Object>> getUserVorlieben(
|
||||||
|
@PathVariable UUID userId, Principal principal) {
|
||||||
|
UserEntity targetUser = userRepository.findById(userId).orElse(null);
|
||||||
|
if (targetUser == null) return ResponseEntity.notFound().build();
|
||||||
|
|
||||||
|
boolean isOwn = false;
|
||||||
|
boolean isFriend = false;
|
||||||
|
if (principal != null) {
|
||||||
|
UUID myId = userService.requireUser(principal).getUserId();
|
||||||
|
isOwn = myId.equals(userId);
|
||||||
|
if (!isOwn) {
|
||||||
|
Optional<FriendshipEntity> f = friendshipRepository.findExisting(myId, userId);
|
||||||
|
isFriend = f.isPresent() && f.get().getStatus() == FriendshipEntity.Status.ACCEPTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Sichtbarkeit sv = targetUser.getSichtbarkeitVorlieben();
|
||||||
|
if (sv == null) sv = Sichtbarkeit.ALLE;
|
||||||
|
|
||||||
|
boolean canSee = isOwn
|
||||||
|
|| sv == Sichtbarkeit.ALLE
|
||||||
|
|| (sv == Sichtbarkeit.NUR_FREUNDE && isFriend);
|
||||||
|
|
||||||
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
|
result.put("sichtbarkeit", sv.name());
|
||||||
|
result.put("canSee", canSee);
|
||||||
|
|
||||||
|
if (canSee) {
|
||||||
|
Map<String, String> ratingsMap = userVorliebeRepository.findByUserId(userId).stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
uv -> uv.getItemId().toString(),
|
||||||
|
uv -> uv.getBewertung().name()));
|
||||||
|
result.put("ratings", ratingsMap);
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,12 +20,12 @@ spring.jpa.properties.hibernate.type.preferred_uuid_jdbc_type=VARCHAR
|
|||||||
#spring.mail.properties.mail.smtp.starttls.enable=false
|
#spring.mail.properties.mail.smtp.starttls.enable=false
|
||||||
|
|
||||||
# Mailpit
|
# Mailpit
|
||||||
spring.mail.host=localhost
|
spring.mail.host=smtp-relay.brevo.com
|
||||||
spring.mail.port=1025
|
spring.mail.port=587
|
||||||
spring.mail.username=
|
spring.mail.username=a6b17a001@smtp-brevo.com
|
||||||
spring.mail.password=
|
spring.mail.password=xsmtpsib-77b691d562154574133d12b09d44a06e166d30091aac6642480771a0ae463a79-8yH3jHOd4nMMAwuS
|
||||||
spring.mail.properties.mail.smtp.auth=false
|
spring.mail.properties.mail.smtp.auth=true
|
||||||
spring.mail.properties.mail.smtp.starttls.enable=false
|
spring.mail.properties.mail.smtp.starttls.enable=true
|
||||||
|
|
||||||
# JWT Keystore
|
# JWT Keystore
|
||||||
jwt.keystore.path=classpath:xxx.jks
|
jwt.keystore.path=classpath:xxx.jks
|
||||||
|
|||||||
13
xxxthegame/src/main/resources/sql/admin.sql
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
● -- Person zur admin-Tabelle als SUPERADMIN hinzufügen (über E-Mail-Adresse)
|
||||||
|
INSERT INTO admin (admin_id, user_id, rolle, created_at)
|
||||||
|
SELECT UUID(), u.user_id, 'SUPERADMIN', NOW()
|
||||||
|
FROM user u
|
||||||
|
WHERE u.email = 'email@beispiel.de';
|
||||||
|
|
||||||
|
-- Falls der User bereits ein (normaler) Admin ist, Rolle upgraden:
|
||||||
|
UPDATE admin a
|
||||||
|
JOIN user u ON a.user_id = u.user_id
|
||||||
|
SET a.rolle = 'SUPERADMIN'
|
||||||
|
WHERE u.email = 'email@beispiel.de';
|
||||||
|
|
||||||
|
--Einfach email@beispiel.de durch die Ziel-E-Mail ersetzen. Das erste Statement fügt einen neuen Admin-Eintrag ein, das zweite upgraded einen bestehenden. Nur eines von beiden ausführen je nach Fall.
|
||||||
@@ -465,6 +465,7 @@
|
|||||||
<button class="tab-btn" data-tab="feedback">Feedback</button>
|
<button class="tab-btn" data-tab="feedback">Feedback</button>
|
||||||
<button class="tab-btn" data-tab="aufgabengruppen">Aufgabengruppen</button>
|
<button class="tab-btn" data-tab="aufgabengruppen">Aufgabengruppen</button>
|
||||||
<button class="tab-btn" data-tab="toys">Toys</button>
|
<button class="tab-btn" data-tab="toys">Toys</button>
|
||||||
|
<button class="tab-btn" data-tab="vorlieben">Vorlieben</button>
|
||||||
<button class="tab-btn superadmin-only" data-tab="admins">Admins</button>
|
<button class="tab-btn superadmin-only" data-tab="admins">Admins</button>
|
||||||
<button class="tab-btn superadmin-only" data-tab="abonnements">Abonnements</button>
|
<button class="tab-btn superadmin-only" data-tab="abonnements">Abonnements</button>
|
||||||
<button class="tab-btn superadmin-only" data-tab="schnittstellen">Schnittstellen</button>
|
<button class="tab-btn superadmin-only" data-tab="schnittstellen">Schnittstellen</button>
|
||||||
@@ -580,6 +581,47 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ── Vorlieben ── -->
|
||||||
|
<div class="tab-panel" id="panel-vorlieben">
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2 class="section-title">Vorlieben</h2>
|
||||||
|
<div class="section-actions">
|
||||||
|
<button class="btn-action" id="vlExportBtn">⬇ Export</button>
|
||||||
|
<button class="btn-action" id="vlImportBtn">⬆ Import</button>
|
||||||
|
<input type="file" id="vlImportFile" accept=".json" style="display:none">
|
||||||
|
<button class="btn-add" id="vlKatCreateBtn">+ Neue Kategorie</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="action-error" id="vlError"></div>
|
||||||
|
<!-- Kategorie-Formular -->
|
||||||
|
<div class="form-section" id="vlKatForm" style="display:none;">
|
||||||
|
<h3 id="vlKatFormTitle">Kategorie anlegen</h3>
|
||||||
|
<div class="form-row">
|
||||||
|
<input type="hidden" id="vlKatId">
|
||||||
|
<input type="text" id="vlKatName" placeholder="Name der Kategorie">
|
||||||
|
<input type="number" id="vlKatSort" placeholder="Reihenfolge (0=oben)" value="0" min="0" style="flex:0 0 160px;">
|
||||||
|
<button onclick="saveKategorie()">Speichern</button>
|
||||||
|
<button class="secondary" onclick="cancelKategorie()">Abbrechen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Item-Formular -->
|
||||||
|
<div class="form-section" id="vlItemForm" style="display:none;">
|
||||||
|
<h3 id="vlItemFormTitle">Vorliebe anlegen</h3>
|
||||||
|
<div class="form-row">
|
||||||
|
<input type="hidden" id="vlItemId">
|
||||||
|
<select id="vlItemKat" style="flex:0 0 220px;"></select>
|
||||||
|
<input type="text" id="vlItemName" placeholder="Name der Vorliebe">
|
||||||
|
<input type="number" id="vlItemSort" placeholder="Reihenfolge" value="0" min="0" style="flex:0 0 140px;">
|
||||||
|
<button onclick="saveItem()">Speichern</button>
|
||||||
|
<button class="secondary" onclick="cancelItem()">Abbrechen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Kategorien-Liste -->
|
||||||
|
<div class="gruppe-list" id="vlKatList"><p class="empty-hint">Wird geladen…</p></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ── Admins (nur Superadmin) ── -->
|
<!-- ── Admins (nur Superadmin) ── -->
|
||||||
<div class="tab-panel superadmin-only" id="panel-admins">
|
<div class="tab-panel superadmin-only" id="panel-admins">
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
@@ -2337,6 +2379,225 @@ function escAdminHtml(s) {
|
|||||||
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Vorlieben-Verwaltung ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
let _vlKategorien = [];
|
||||||
|
let _vlItems = [];
|
||||||
|
|
||||||
|
async function loadVorliebenAdmin() {
|
||||||
|
const [rKat, rItem] = await Promise.all([
|
||||||
|
fetch('/admin/vorlieben/kategorien'),
|
||||||
|
fetch('/admin/vorlieben/items'),
|
||||||
|
]);
|
||||||
|
if (!rKat.ok || !rItem.ok) return;
|
||||||
|
_vlKategorien = await rKat.json();
|
||||||
|
_vlItems = await rItem.json();
|
||||||
|
renderVlListe();
|
||||||
|
renderVlKatDropdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderVlListe() {
|
||||||
|
const container = document.getElementById('vlKatList');
|
||||||
|
if (!_vlKategorien.length) {
|
||||||
|
container.innerHTML = '<p class="empty-hint">Keine Kategorien vorhanden. Lege zuerst eine Kategorie an.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const itemsByKat = {};
|
||||||
|
_vlKategorien.forEach(k => { itemsByKat[k.kategorieId] = []; });
|
||||||
|
_vlItems.forEach(i => { if (itemsByKat[i.kategorieId]) itemsByKat[i.kategorieId].push(i); });
|
||||||
|
|
||||||
|
container.innerHTML = _vlKategorien.map(k => {
|
||||||
|
const items = itemsByKat[k.kategorieId] || [];
|
||||||
|
const itemsHtml = items.length
|
||||||
|
? `<div class="item-list">${items.map(i => `
|
||||||
|
<div class="item">
|
||||||
|
<div class="item-row" style="cursor:default;">
|
||||||
|
<span class="item-text">${escAdminHtml(i.name)}</span>
|
||||||
|
<span style="font-size:0.72rem;color:var(--color-muted);flex-shrink:0;margin-right:0.5rem;">#${i.sortOrder}</span>
|
||||||
|
<div class="item-badges" style="flex-shrink:0;">
|
||||||
|
<button class="btn-item-edit" onclick="editItem('${i.itemId}')">✎</button>
|
||||||
|
<button class="btn-item-delete" onclick="deleteItem('${i.itemId}','${escAdminHtml(i.name).replace(/'/g,"\\'")}')">✕</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`).join('')}</div>`
|
||||||
|
: `<p class="sub-empty">Keine Vorlieben in dieser Kategorie.</p>`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="gruppe-card open" id="vlkat-${k.kategorieId}">
|
||||||
|
<div class="gruppe-header" onclick="this.closest('.gruppe-card').classList.toggle('open')">
|
||||||
|
<div class="gruppe-meta">
|
||||||
|
<div class="gruppe-name">${escAdminHtml(k.name)}</div>
|
||||||
|
<div class="gruppe-info">${items.length} Vorliebe${items.length !== 1 ? 'n' : ''} · Reihenfolge: ${k.sortOrder}</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:0.4rem;align-items:center;flex-shrink:0;">
|
||||||
|
<button class="btn-item-edit" onclick="event.stopPropagation();addItemToKat('${k.kategorieId}')">+ Vorliebe</button>
|
||||||
|
<button class="btn-item-edit" onclick="event.stopPropagation();editKategorie('${k.kategorieId}')">✎</button>
|
||||||
|
<button class="btn-item-delete" onclick="event.stopPropagation();deleteKategorie('${k.kategorieId}','${escAdminHtml(k.name).replace(/'/g,"\\'")}')">✕</button>
|
||||||
|
<span class="gruppe-toggle">▶</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="gruppe-body">
|
||||||
|
<div class="sub-section">
|
||||||
|
<div class="sub-section-header">
|
||||||
|
<span class="sub-section-title">Vorlieben</span>
|
||||||
|
</div>
|
||||||
|
${itemsHtml}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderVlKatDropdown() {
|
||||||
|
const sel = document.getElementById('vlItemKat');
|
||||||
|
const cur = sel.value;
|
||||||
|
sel.innerHTML = _vlKategorien.map(k =>
|
||||||
|
`<option value="${k.kategorieId}">${escAdminHtml(k.name)}</option>`).join('');
|
||||||
|
if (cur) sel.value = cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Kategorien CRUD ──
|
||||||
|
document.getElementById('vlKatCreateBtn').addEventListener('click', () => {
|
||||||
|
document.getElementById('vlKatId').value = '';
|
||||||
|
document.getElementById('vlKatName').value = '';
|
||||||
|
document.getElementById('vlKatSort').value = '0';
|
||||||
|
document.getElementById('vlKatFormTitle').textContent = 'Kategorie anlegen';
|
||||||
|
document.getElementById('vlItemForm').style.display = 'none';
|
||||||
|
document.getElementById('vlKatForm').style.display = '';
|
||||||
|
document.getElementById('vlKatName').focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
function cancelKategorie() { document.getElementById('vlKatForm').style.display = 'none'; }
|
||||||
|
|
||||||
|
function editKategorie(id) {
|
||||||
|
const k = _vlKategorien.find(x => x.kategorieId === id);
|
||||||
|
if (!k) return;
|
||||||
|
document.getElementById('vlKatId').value = k.kategorieId;
|
||||||
|
document.getElementById('vlKatName').value = k.name;
|
||||||
|
document.getElementById('vlKatSort').value = k.sortOrder;
|
||||||
|
document.getElementById('vlKatFormTitle').textContent = 'Kategorie bearbeiten';
|
||||||
|
document.getElementById('vlItemForm').style.display = 'none';
|
||||||
|
document.getElementById('vlKatForm').style.display = '';
|
||||||
|
document.getElementById('vlKatName').focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveKategorie() {
|
||||||
|
const id = document.getElementById('vlKatId').value;
|
||||||
|
const name = document.getElementById('vlKatName').value.trim();
|
||||||
|
const sort = parseInt(document.getElementById('vlKatSort').value) || 0;
|
||||||
|
const errEl = document.getElementById('vlError');
|
||||||
|
if (!name) { errEl.textContent = 'Name darf nicht leer sein.'; return; }
|
||||||
|
const url = id ? `/admin/vorlieben/kategorien/${id}` : '/admin/vorlieben/kategorien';
|
||||||
|
const method = id ? 'PUT' : 'POST';
|
||||||
|
const r = await fetch(url, { method, headers: {'Content-Type':'application/json'},
|
||||||
|
body: JSON.stringify({ name, sortOrder: sort }) });
|
||||||
|
if (r.ok || r.status === 201) {
|
||||||
|
errEl.textContent = '';
|
||||||
|
document.getElementById('vlKatForm').style.display = 'none';
|
||||||
|
await loadVorliebenAdmin();
|
||||||
|
} else { errEl.textContent = 'Fehler beim Speichern.'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteKategorie(id, name) {
|
||||||
|
if (!confirm(`Kategorie "${name}" löschen?`)) return;
|
||||||
|
const errEl = document.getElementById('vlError');
|
||||||
|
const r = await fetch(`/admin/vorlieben/kategorien/${id}`, { method: 'DELETE' });
|
||||||
|
if (r.ok) { await loadVorliebenAdmin(); }
|
||||||
|
else if (r.status === 409) { errEl.textContent = 'Kategorie enthält noch Vorlieben – bitte zuerst alle Vorlieben dieser Kategorie löschen.'; }
|
||||||
|
else { errEl.textContent = 'Fehler beim Löschen.'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Items CRUD ──
|
||||||
|
document.getElementById('vlKatCreateBtn'); // already bound above
|
||||||
|
|
||||||
|
function addItemToKat(katId) {
|
||||||
|
document.getElementById('vlItemId').value = '';
|
||||||
|
document.getElementById('vlItemKat').value = katId;
|
||||||
|
document.getElementById('vlItemName').value = '';
|
||||||
|
document.getElementById('vlItemSort').value = '0';
|
||||||
|
document.getElementById('vlItemFormTitle').textContent = 'Vorliebe anlegen';
|
||||||
|
document.getElementById('vlKatForm').style.display = 'none';
|
||||||
|
document.getElementById('vlItemForm').style.display = '';
|
||||||
|
document.getElementById('vlItemName').focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelItem() { document.getElementById('vlItemForm').style.display = 'none'; }
|
||||||
|
|
||||||
|
function editItem(id) {
|
||||||
|
const i = _vlItems.find(x => x.itemId === id);
|
||||||
|
if (!i) return;
|
||||||
|
document.getElementById('vlItemId').value = i.itemId;
|
||||||
|
document.getElementById('vlItemKat').value = i.kategorieId;
|
||||||
|
document.getElementById('vlItemName').value = i.name;
|
||||||
|
document.getElementById('vlItemSort').value = i.sortOrder;
|
||||||
|
document.getElementById('vlItemFormTitle').textContent = 'Vorliebe bearbeiten';
|
||||||
|
document.getElementById('vlKatForm').style.display = 'none';
|
||||||
|
document.getElementById('vlItemForm').style.display = '';
|
||||||
|
document.getElementById('vlItemName').focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveItem() {
|
||||||
|
const id = document.getElementById('vlItemId').value;
|
||||||
|
const katId = document.getElementById('vlItemKat').value;
|
||||||
|
const name = document.getElementById('vlItemName').value.trim();
|
||||||
|
const sort = parseInt(document.getElementById('vlItemSort').value) || 0;
|
||||||
|
const errEl = document.getElementById('vlError');
|
||||||
|
if (!name || !katId) { errEl.textContent = 'Name und Kategorie sind erforderlich.'; return; }
|
||||||
|
const url = id ? `/admin/vorlieben/items/${id}` : '/admin/vorlieben/items';
|
||||||
|
const method = id ? 'PUT' : 'POST';
|
||||||
|
const r = await fetch(url, { method, headers: {'Content-Type':'application/json'},
|
||||||
|
body: JSON.stringify({ kategorieId: katId, name, sortOrder: sort }) });
|
||||||
|
if (r.ok || r.status === 201) {
|
||||||
|
errEl.textContent = '';
|
||||||
|
document.getElementById('vlItemForm').style.display = 'none';
|
||||||
|
await loadVorliebenAdmin();
|
||||||
|
} else { errEl.textContent = 'Fehler beim Speichern.'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteItem(id, name) {
|
||||||
|
if (!confirm(`Vorliebe "${name}" löschen? Alle Nutzerbewertungen werden ebenfalls gelöscht.`)) return;
|
||||||
|
const errEl = document.getElementById('vlError');
|
||||||
|
const r = await fetch(`/admin/vorlieben/items/${id}`, { method: 'DELETE' });
|
||||||
|
if (r.ok) { await loadVorliebenAdmin(); }
|
||||||
|
else { errEl.textContent = 'Fehler beim Löschen.'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Export / Import ──
|
||||||
|
document.getElementById('vlExportBtn').addEventListener('click', async () => {
|
||||||
|
const r = await fetch('/admin/vorlieben/export');
|
||||||
|
if (!r.ok) { document.getElementById('vlError').textContent = 'Export fehlgeschlagen.'; return; }
|
||||||
|
const blob = await r.blob();
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a'); a.href = url; a.download = 'vorlieben-export.json';
|
||||||
|
document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
const vlImportFile = document.getElementById('vlImportFile');
|
||||||
|
document.getElementById('vlImportBtn').addEventListener('click', () => vlImportFile.click());
|
||||||
|
vlImportFile.addEventListener('change', async function () {
|
||||||
|
if (!this.files.length) return;
|
||||||
|
const file = this.files[0]; this.value = '';
|
||||||
|
const errEl = document.getElementById('vlError');
|
||||||
|
errEl.style.color = 'var(--color-muted)'; errEl.textContent = 'Importiere…';
|
||||||
|
let data;
|
||||||
|
try { data = JSON.parse(await file.text()); }
|
||||||
|
catch(e) { errEl.style.color = ''; errEl.textContent = 'Fehler: Ungültige JSON-Datei.'; return; }
|
||||||
|
const r = await fetch('/admin/vorlieben/import', {
|
||||||
|
method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
if (!r.ok) { errEl.style.color = ''; errEl.textContent = 'Import fehlgeschlagen.'; return; }
|
||||||
|
const res = await r.json();
|
||||||
|
await loadVorliebenAdmin();
|
||||||
|
errEl.style.color = 'var(--color-success,#2ecc71)';
|
||||||
|
errEl.textContent = `Import: ${res.kategorienCreated} Kategorien neu, ${res.itemsCreated} Vorlieben neu, ${res.kategorienSkipped + res.itemsSkipped} übersprungen.`;
|
||||||
|
setTimeout(() => { errEl.textContent = ''; errEl.style.color = ''; }, 5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Vorlieben-Tab beim ersten Öffnen laden
|
||||||
|
document.querySelector('.tab-btn[data-tab="vorlieben"]')?.addEventListener('click', () => {
|
||||||
|
if (!_vlKategorien.length && !_vlItems.length) loadVorliebenAdmin();
|
||||||
|
});
|
||||||
|
|
||||||
init();
|
init();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -347,6 +347,27 @@
|
|||||||
.profil-tab-panel { display:none; }
|
.profil-tab-panel { display:none; }
|
||||||
.profil-tab-panel.active { display:block; }
|
.profil-tab-panel.active { display:block; }
|
||||||
|
|
||||||
|
/* ── Vorlieben Tab ── */
|
||||||
|
.vorlieben-group { margin-bottom:1.25rem; }
|
||||||
|
.vorlieben-group-title {
|
||||||
|
font-size:0.78rem; font-weight:700; color:var(--color-muted);
|
||||||
|
text-transform:uppercase; letter-spacing:0.05em;
|
||||||
|
margin-bottom:0.5rem; padding-bottom:0.3rem;
|
||||||
|
border-bottom:1px solid var(--color-secondary);
|
||||||
|
}
|
||||||
|
.vorlieben-chips { display:flex; flex-wrap:wrap; gap:0.4rem; }
|
||||||
|
.vorliebe-chip {
|
||||||
|
display:inline-block; padding:0.25rem 0.65rem; border-radius:999px;
|
||||||
|
font-size:0.82rem; border:1px solid var(--color-secondary);
|
||||||
|
background:var(--color-card); color:var(--color-text);
|
||||||
|
}
|
||||||
|
.vorliebe-chip.bw-UNBEDINGT { border-color:#2e7d32; color:#2e7d32; }
|
||||||
|
.vorliebe-chip.bw-MAG_ICH { border-color:#81c784; color:#81c784; }
|
||||||
|
.vorliebe-chip.bw-WILL_AUSPROBIEREN{ border-color:#1e88e5; color:#1e88e5; }
|
||||||
|
.vorliebe-chip.bw-NEUTRAL { border-color:#fdd835; color:#fdd835; }
|
||||||
|
.vorliebe-chip.bw-EHER_NICHT { border-color:#fb8c00; color:#fb8c00; }
|
||||||
|
.vorliebe-chip.bw-GEHT_GAR_NICHT { border-color:#e53935; color:#e53935; }
|
||||||
|
|
||||||
/* ── Post cards (profile posts tab) ── */
|
/* ── Post cards (profile posts tab) ── */
|
||||||
.post-card { background:var(--color-card); border:1px solid var(--color-secondary); border-radius:10px; padding:1rem; margin-bottom:0.9rem; }
|
.post-card { background:var(--color-card); border:1px solid var(--color-secondary); border-radius:10px; padding:1rem; margin-bottom:0.9rem; }
|
||||||
.post-header { display:flex; align-items:center; gap:0.7rem; margin-bottom:0.6rem; }
|
.post-header { display:flex; align-items:center; gap:0.7rem; margin-bottom:0.6rem; }
|
||||||
@@ -434,10 +455,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tabs: Feed | Pinnwand | Spielhistorie | Keyholder-Angebote -->
|
<!-- Tabs: Feed | Pinnwand | Vorlieben | Spielhistorie | Keyholder-Angebote -->
|
||||||
<div class="profil-tabs" style="margin-top:1.25rem;">
|
<div class="profil-tabs" style="margin-top:1.25rem;">
|
||||||
<button class="profil-tab-btn active" id="tabBtnPosts" onclick="switchProfilTab('posts', this)">Feed</button>
|
<button class="profil-tab-btn active" id="tabBtnPosts" onclick="switchProfilTab('posts', this)">Feed</button>
|
||||||
<button class="profil-tab-btn" id="tabBtnPinnwand" onclick="switchProfilTab('pinnwand', this)">Pinnwand</button>
|
<button class="profil-tab-btn" id="tabBtnPinnwand" onclick="switchProfilTab('pinnwand', this)">Pinnwand</button>
|
||||||
|
<button class="profil-tab-btn" id="tabBtnVorlieben" onclick="switchProfilTab('vorlieben', this)" style="display:none;">Vorlieben</button>
|
||||||
<button class="profil-tab-btn" id="tabBtnGameHistory" onclick="switchProfilTab('gamehistory', this)">Spielhistorie</button>
|
<button class="profil-tab-btn" id="tabBtnGameHistory" onclick="switchProfilTab('gamehistory', this)">Spielhistorie</button>
|
||||||
<button class="profil-tab-btn" id="tabBtnKhOffers" onclick="switchProfilTab('khoffers', this)">Keyholder-Angebote</button>
|
<button class="profil-tab-btn" id="tabBtnKhOffers" onclick="switchProfilTab('khoffers', this)">Keyholder-Angebote</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -458,6 +480,11 @@
|
|||||||
<div id="pinnwandList"></div>
|
<div id="pinnwandList"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Vorlieben Tab -->
|
||||||
|
<div class="profil-tab-panel" id="tab-vorlieben">
|
||||||
|
<div id="vorliebenDisplay" style="margin-top:0.75rem;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Spielhistorie Tab -->
|
<!-- Spielhistorie Tab -->
|
||||||
<div class="profil-tab-panel" id="tab-gamehistory">
|
<div class="profil-tab-panel" id="tab-gamehistory">
|
||||||
<div id="gameHistoryList" style="margin-top:0.75rem;"></div>
|
<div id="gameHistoryList" style="margin-top:0.75rem;"></div>
|
||||||
@@ -681,17 +708,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyTabPrivacy(profile, isFriend) {
|
function applyTabPrivacy(profile, isFriend) {
|
||||||
const showFeed = canSee(profile.sichtbarkeitFeed, isFriend, isOwnProfile);
|
const showFeed = canSee(profile.sichtbarkeitFeed, isFriend, isOwnProfile);
|
||||||
const showPinnwand = canSee(profile.sichtbarkeitPinnwand, isFriend, isOwnProfile);
|
const showPinnwand = canSee(profile.sichtbarkeitPinnwand, isFriend, isOwnProfile);
|
||||||
const showHistory = canSee(profile.sichtbarkeitLockhistorie, isFriend, isOwnProfile);
|
const showHistory = canSee(profile.sichtbarkeitLockhistorie, isFriend, isOwnProfile);
|
||||||
|
const showVorlieben = canSee(profile.sichtbarkeitVorlieben, isFriend, isOwnProfile);
|
||||||
|
|
||||||
const btnFeed = document.getElementById('tabBtnPosts');
|
const btnFeed = document.getElementById('tabBtnPosts');
|
||||||
const btnPinnwand = document.getElementById('tabBtnPinnwand');
|
const btnPinnwand = document.getElementById('tabBtnPinnwand');
|
||||||
const btnHistory = document.getElementById('tabBtnGameHistory');
|
const btnHistory = document.getElementById('tabBtnGameHistory');
|
||||||
|
const btnVorlieben = document.getElementById('tabBtnVorlieben');
|
||||||
|
|
||||||
if (!showFeed) { btnFeed.style.display = 'none'; document.getElementById('tab-posts').classList.remove('active'); }
|
if (!showFeed) { btnFeed.style.display = 'none'; document.getElementById('tab-posts').classList.remove('active'); }
|
||||||
if (!showPinnwand) { btnPinnwand.style.display = 'none'; }
|
if (!showPinnwand) { btnPinnwand.style.display = 'none'; }
|
||||||
if (!showHistory) { btnHistory.style.display = 'none'; }
|
if (!showHistory) { btnHistory.style.display = 'none'; }
|
||||||
|
if (showVorlieben) { btnVorlieben.style.display = ''; loadVorlieben(); }
|
||||||
|
|
||||||
// Ersten sichtbaren Tab aktivieren
|
// Ersten sichtbaren Tab aktivieren
|
||||||
if (!showFeed) {
|
if (!showFeed) {
|
||||||
@@ -707,6 +737,64 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Vorlieben anzeigen ──
|
||||||
|
const BEWERTUNG_ORDER = ['UNBEDINGT','MAG_ICH','WILL_AUSPROBIEREN','NEUTRAL','EHER_NICHT','GEHT_GAR_NICHT'];
|
||||||
|
const BEWERTUNG_LABEL = {
|
||||||
|
UNBEDINGT: 'Unbedingt', MAG_ICH: 'Mag ich',
|
||||||
|
WILL_AUSPROBIEREN: 'Will ich ausprobieren', NEUTRAL: 'Neutral',
|
||||||
|
EHER_NICHT: 'Eher nicht', GEHT_GAR_NICHT: 'Geht gar nicht',
|
||||||
|
};
|
||||||
|
let _vorliebenLoaded = false;
|
||||||
|
|
||||||
|
async function loadVorlieben() {
|
||||||
|
if (_vorliebenLoaded) return;
|
||||||
|
_vorliebenLoaded = true;
|
||||||
|
const container = document.getElementById('vorliebenDisplay');
|
||||||
|
container.innerHTML = '<p style="color:var(--color-muted);font-size:0.85rem;">Wird geladen…</p>';
|
||||||
|
try {
|
||||||
|
const [dataRes, itemsRes] = await Promise.all([
|
||||||
|
fetch('/vorlieben/user/' + targetUserId),
|
||||||
|
fetch('/vorlieben/items'),
|
||||||
|
]);
|
||||||
|
if (!dataRes.ok || !itemsRes.ok) { container.innerHTML = '<p style="color:var(--color-muted);font-size:0.85rem;">Fehler beim Laden.</p>'; return; }
|
||||||
|
const data = await dataRes.json();
|
||||||
|
const kategorien = await itemsRes.json();
|
||||||
|
|
||||||
|
if (!data.canSee) {
|
||||||
|
container.innerHTML = '<p style="color:var(--color-muted);font-size:0.85rem;">Nicht sichtbar.</p>'; return;
|
||||||
|
}
|
||||||
|
const ratings = data.ratings || {};
|
||||||
|
|
||||||
|
// Build itemId → name map
|
||||||
|
const itemNames = {};
|
||||||
|
kategorien.forEach(k => k.items.forEach(i => { itemNames[i.itemId] = i.name; }));
|
||||||
|
|
||||||
|
// Group by bewertung
|
||||||
|
const grouped = {};
|
||||||
|
Object.entries(ratings).forEach(([itemId, bw]) => {
|
||||||
|
if (!grouped[bw]) grouped[bw] = [];
|
||||||
|
grouped[bw].push(itemNames[itemId] || itemId);
|
||||||
|
});
|
||||||
|
|
||||||
|
const visibleGroups = BEWERTUNG_ORDER.filter(bw => grouped[bw]?.length);
|
||||||
|
if (!visibleGroups.length) {
|
||||||
|
container.innerHTML = '<p style="color:var(--color-muted);font-size:0.85rem;">Keine Vorlieben angegeben.</p>'; return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = visibleGroups.map(bw => `
|
||||||
|
<div class="vorlieben-group">
|
||||||
|
<div class="vorlieben-group-title">${BEWERTUNG_LABEL[bw]}</div>
|
||||||
|
<div class="vorlieben-chips">
|
||||||
|
${grouped[bw].map(name =>
|
||||||
|
`<span class="vorliebe-chip bw-${bw}">${escV(name)}</span>`).join('')}
|
||||||
|
</div>
|
||||||
|
</div>`).join('');
|
||||||
|
} catch(e) {
|
||||||
|
container.innerHTML = '<p style="color:var(--color-muted);font-size:0.85rem;">Fehler beim Laden.</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function escV(s) { return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
||||||
|
|
||||||
function showPreviewBanner(mode) {
|
function showPreviewBanner(mode) {
|
||||||
const banner = document.createElement('div');
|
const banner = document.createElement('div');
|
||||||
banner.style.cssText = 'background:var(--color-secondary);border:1px solid var(--color-primary);border-radius:8px;padding:0.65rem 1rem;margin-bottom:1rem;font-size:0.88rem;display:flex;align-items:center;justify-content:space-between;gap:0.75rem;';
|
banner.style.cssText = 'background:var(--color-secondary);border:1px solid var(--color-primary);border-radius:8px;padding:0.65rem 1rem;margin-bottom:1rem;font-size:0.88rem;display:flex;align-items:center;justify-content:space-between;gap:0.75rem;';
|
||||||
|
|||||||
@@ -103,6 +103,7 @@
|
|||||||
.aufgaben-section-label { font-size: 0.78rem; font-weight: 600; color: var(--color-muted); text-transform: uppercase; letter-spacing: 0.04em; margin: 1rem 0 0.5rem 0; }
|
.aufgaben-section-label { font-size: 0.78rem; font-weight: 600; color: var(--color-muted); text-transform: uppercase; letter-spacing: 0.04em; margin: 1rem 0 0.5rem 0; }
|
||||||
.aufgaben-section-label:first-child { margin-top: 0; }
|
.aufgaben-section-label:first-child { margin-top: 0; }
|
||||||
.toys-hint { font-size: 0.85rem; color: var(--color-muted); margin-bottom: 1rem; }
|
.toys-hint { font-size: 0.85rem; color: var(--color-muted); margin-bottom: 1rem; }
|
||||||
|
.toys-badge { font-size: 0.75rem; font-weight: 600; background: var(--color-primary); color: #fff; border-radius: 999px; padding: 0.1em 0.55em; margin-left: 0.5rem; vertical-align: middle; }
|
||||||
|
|
||||||
/* ── Guest hint ── */
|
/* ── Guest hint ── */
|
||||||
.guest-hint { font-size: 0.8rem; color: var(--color-muted); font-style: italic; margin-bottom: 0.75rem; padding: 0.5rem 0.75rem; background: var(--color-secondary); border-radius: 6px; }
|
.guest-hint { font-size: 0.8rem; color: var(--color-muted); font-style: italic; margin-bottom: 0.75rem; padding: 0.5rem 0.75rem; background: var(--color-secondary); border-radius: 6px; }
|
||||||
@@ -210,7 +211,7 @@
|
|||||||
<!-- Accordion 4: Toys -->
|
<!-- Accordion 4: Toys -->
|
||||||
<div class="acc-item">
|
<div class="acc-item">
|
||||||
<button class="acc-header" id="acc-toys-btn" onclick="toggleAcc('toys')">
|
<button class="acc-header" id="acc-toys-btn" onclick="toggleAcc('toys')">
|
||||||
<span>Toys</span><span class="acc-chevron">▾</span>
|
<span>Toys<span id="toys-badge" class="toys-badge" style="display:none;"></span></span><span class="acc-chevron">▾</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="acc-body" id="acc-toys-body">
|
<div class="acc-body" id="acc-toys-body">
|
||||||
<div id="guestToysHint" class="guest-hint" style="display:none;">Toys werden vom Host festgelegt – nur zur Ansicht.</div>
|
<div id="guestToysHint" class="guest-hint" style="display:none;">Toys werden vom Host festgelegt – nur zur Ansicht.</div>
|
||||||
@@ -536,6 +537,8 @@
|
|||||||
function onGruppenChanged() {
|
function onGruppenChanged() {
|
||||||
warnungsAkzeptiert = false; hideMessage();
|
warnungsAkzeptiert = false; hideMessage();
|
||||||
gruppenContent = null; loadedForGruppen = null; toysNeedReload = true;
|
gruppenContent = null; loadedForGruppen = null; toysNeedReload = true;
|
||||||
|
const badge = document.getElementById('toys-badge');
|
||||||
|
badge.style.display = 'none'; badge.textContent = '';
|
||||||
if (document.getElementById('acc-toys-body').classList.contains('is-open')) ladeToys();
|
if (document.getElementById('acc-toys-body').classList.contains('is-open')) ladeToys();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,23 +608,35 @@
|
|||||||
|
|
||||||
async function ladeToys() {
|
async function ladeToys() {
|
||||||
const toyList = document.getElementById('toyList');
|
const toyList = document.getElementById('toyList');
|
||||||
toyList.innerHTML = '<p class="empty-hint">Lade…</p>';
|
const selected = getSelectedGruppen();
|
||||||
const content = await ladeGruppenContent();
|
if (!selected.length) {
|
||||||
const toyMap = new Map();
|
toyList.innerHTML = '<p class="empty-hint">Bitte zuerst Aufgaben-Gruppen auswählen.</p>';
|
||||||
[...content.aufgaben, ...content.finisher].forEach(item =>
|
toysNeedReload = false; return;
|
||||||
(item.benoetigteToys || []).forEach(t => { if (!toyMap.has(t.toyId)) toyMap.set(t.toyId, t); }));
|
}
|
||||||
const toys = [...toyMap.values()].sort((a, b) => a.name.localeCompare(b.name));
|
toyList.innerHTML = '<p class="empty-hint">Lade…</p>';
|
||||||
if (!toys.length) {
|
try {
|
||||||
toyList.innerHTML = '<p class="empty-hint">Keine Toys erforderlich – alle Aufgaben können gespielt werden.</p>'; return;
|
const params = selected.map(id => `gruppenIds=${encodeURIComponent(id)}`).join('&');
|
||||||
|
const res = await fetch(`/vanilla/toy/required?${params}`);
|
||||||
|
if (!res.ok) throw new Error();
|
||||||
|
const toys = await res.json();
|
||||||
|
toysNeedReload = false;
|
||||||
|
const badge = document.getElementById('toys-badge');
|
||||||
|
if (toys.length) { badge.textContent = toys.length; badge.style.display = ''; }
|
||||||
|
else { badge.style.display = 'none'; badge.textContent = ''; }
|
||||||
|
if (!toys.length) {
|
||||||
|
toyList.innerHTML = '<p class="empty-hint">Keine Toys erforderlich – alle Aufgaben können gespielt werden.</p>'; return;
|
||||||
|
}
|
||||||
|
toyList.innerHTML = toys.map(toy => {
|
||||||
|
const checked = savedToyIds === null || savedToyIds.has(toy.toyId);
|
||||||
|
return `<label class="toy-item${checked ? ' is-checked' : ''}">
|
||||||
|
<input type="checkbox" value="${toy.toyId}"${checked ? ' checked' : ''}>
|
||||||
|
<span><span class="toy-item-name">${toy.name}</span>${toy.beschreibung ? `<span class="toy-item-desc">${toy.beschreibung}</span>` : ''}</span>
|
||||||
|
${toy.bild ? `<img class="item-img" src="data:image/png;base64,${toy.bild}" alt="">` : ''}
|
||||||
|
</label>`;
|
||||||
|
}).join('');
|
||||||
|
} catch (_) {
|
||||||
|
toyList.innerHTML = '<p class="empty-hint">Fehler beim Laden der Toys.</p>';
|
||||||
}
|
}
|
||||||
toyList.innerHTML = toys.map(toy => {
|
|
||||||
const checked = savedToyIds === null || savedToyIds.has(toy.toyId);
|
|
||||||
return `<label class="toy-item${checked ? ' is-checked' : ''}">
|
|
||||||
<input type="checkbox" value="${toy.toyId}"${checked ? ' checked' : ''}>
|
|
||||||
<span><span class="toy-item-name">${toy.name}</span>${toy.beschreibung ? `<span class="toy-item-desc">${toy.beschreibung}</span>` : ''}</span>
|
|
||||||
${toy.bild ? `<img class="item-img" src="data:image/png;base64,${toy.bild}" alt="">` : ''}
|
|
||||||
</label>`;
|
|
||||||
}).join('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateContent(content, mitspieler) {
|
function validateContent(content, mitspieler) {
|
||||||
|
|||||||
BIN
xxxthegame/src/main/resources/static/img/vorlieben/dunno.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
xxxthegame/src/main/resources/static/img/vorlieben/negative.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
xxxthegame/src/main/resources/static/img/vorlieben/neutral.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
xxxthegame/src/main/resources/static/img/vorlieben/positiv.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 20 KiB |
@@ -566,6 +566,19 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Vorlieben -->
|
||||||
|
<div class="settings-row">
|
||||||
|
<div class="settings-row-info">
|
||||||
|
<div class="settings-row-label">Vorlieben</div>
|
||||||
|
<div class="settings-row-desc">Deine Vorlieben und Bewertungen im Profil</div>
|
||||||
|
</div>
|
||||||
|
<select id="sv-vorlieben" onchange="doSave()">
|
||||||
|
<option value="ALLE">Alle</option>
|
||||||
|
<option value="NUR_FREUNDE">Nur Freunde</option>
|
||||||
|
<option value="NUR_ICH">Nur ich</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Profil bei Veröffentlichungen -->
|
<!-- Profil bei Veröffentlichungen -->
|
||||||
<div class="settings-row">
|
<div class="settings-row">
|
||||||
<div class="settings-row-info">
|
<div class="settings-row-info">
|
||||||
@@ -974,6 +987,7 @@
|
|||||||
setValue('sv-pinnwand', profile.sichtbarkeitPinnwand || 'ALLE');
|
setValue('sv-pinnwand', profile.sichtbarkeitPinnwand || 'ALLE');
|
||||||
setValue('sv-xp', profile.sichtbarkeitXp || 'ALLE');
|
setValue('sv-xp', profile.sichtbarkeitXp || 'ALLE');
|
||||||
setValue('sv-lockhistorie', profile.sichtbarkeitLockhistorie || 'ALLE');
|
setValue('sv-lockhistorie', profile.sichtbarkeitLockhistorie || 'ALLE');
|
||||||
|
setValue('sv-vorlieben', profile.sichtbarkeitVorlieben || 'ALLE');
|
||||||
setValue('sv-veroeffentlichungen', profile.profilBeiVeroeffentlichungenSichtbar ? 'true' : 'false');
|
setValue('sv-veroeffentlichungen', profile.profilBeiVeroeffentlichungenSichtbar ? 'true' : 'false');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -991,6 +1005,7 @@
|
|||||||
sichtbarkeitPinnwand: document.getElementById('sv-pinnwand').value,
|
sichtbarkeitPinnwand: document.getElementById('sv-pinnwand').value,
|
||||||
sichtbarkeitXp: document.getElementById('sv-xp').value,
|
sichtbarkeitXp: document.getElementById('sv-xp').value,
|
||||||
sichtbarkeitLockhistorie: document.getElementById('sv-lockhistorie').value,
|
sichtbarkeitLockhistorie: document.getElementById('sv-lockhistorie').value,
|
||||||
|
sichtbarkeitVorlieben: document.getElementById('sv-vorlieben').value,
|
||||||
profilBeiVeroeffentlichungenSichtbar: document.getElementById('sv-veroeffentlichungen').value === 'true',
|
profilBeiVeroeffentlichungenSichtbar: document.getElementById('sv-veroeffentlichungen').value === 'true',
|
||||||
};
|
};
|
||||||
const res = await fetch('/user/me/privacy', {
|
const res = await fetch('/user/me/privacy', {
|
||||||
|
|||||||
@@ -95,6 +95,39 @@
|
|||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Vorlieben Tabs ── */
|
||||||
|
.vl-tabs { display: flex; gap: 0; flex-wrap: wrap;
|
||||||
|
border-bottom: 1px solid var(--color-secondary); margin-bottom: 1rem; }
|
||||||
|
.vl-tab-btn { background: none; border: none; border-bottom: 3px solid transparent;
|
||||||
|
border-radius: 0; padding: 0.5rem 1rem; font-size: 0.85rem; font-weight: 600;
|
||||||
|
color: var(--color-muted); cursor: pointer; margin-bottom: -1px;
|
||||||
|
transition: color .15s, border-color .15s; white-space: nowrap; }
|
||||||
|
.vl-tab-btn:hover { color: var(--color-text); background: none; }
|
||||||
|
.vl-tab-btn.active { color: var(--color-primary); border-bottom-color: var(--color-primary); }
|
||||||
|
.vl-tab-panel { display: none; }
|
||||||
|
.vl-tab-panel.active { display: block; }
|
||||||
|
|
||||||
|
/* ── Vorlieben Items ── */
|
||||||
|
.vorliebe-row {
|
||||||
|
display: flex; align-items: center; justify-content: space-between;
|
||||||
|
gap: 0.75rem; padding: 0.55rem 0.75rem; border-radius: 8px;
|
||||||
|
background: var(--color-card); border: 1px solid var(--color-secondary);
|
||||||
|
margin-bottom: 0.45rem;
|
||||||
|
}
|
||||||
|
.vorliebe-row-name { font-size: 0.9rem; flex: 1; min-width: 0; }
|
||||||
|
.vorliebe-smileys { display: flex; gap: 0.25rem; flex-shrink: 0; }
|
||||||
|
.vl-smiley {
|
||||||
|
display: inline-flex; align-items: center; cursor: pointer;
|
||||||
|
border: 2px solid transparent; border-radius: 6px;
|
||||||
|
padding: 0.1rem 0.15rem;
|
||||||
|
transition: border-color .15s, transform .1s;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.vl-smiley img { height: 1.6rem; width: auto; display: block; }
|
||||||
|
.vl-smiley:hover { transform: scale(1.15); }
|
||||||
|
.vl-smiley.active { border-color: var(--vl-color); }
|
||||||
|
.vorlieben-hint { font-size: 0.82rem; color: var(--color-muted); margin-bottom: 1rem; }
|
||||||
|
|
||||||
#ownGallery {
|
#ownGallery {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
@@ -334,6 +367,12 @@
|
|||||||
|
|
||||||
<button class="full-width" id="saveBtn" onclick="saveProfile()">Profil speichern</button>
|
<button class="full-width" id="saveBtn" onclick="saveProfile()">Profil speichern</button>
|
||||||
|
|
||||||
|
<div class="gallery-section-label" style="margin-top:1.5rem;">Vorlieben</div>
|
||||||
|
<p class="vorlieben-hint">Wähle für jede Vorliebe aus, wie du dazu stehst. Nicht ausgefüllte Einträge werden nicht angezeigt.</p>
|
||||||
|
<div id="vorliebenSection"><p style="color:var(--color-muted);font-size:0.85rem;">Wird geladen…</p></div>
|
||||||
|
<div class="message" id="vorliebenMessage" style="margin-top:0.75rem;display:none;"></div>
|
||||||
|
<button class="full-width" id="saveVorliebenBtn" onclick="saveVorlieben()" style="margin-bottom:1.5rem;">Vorlieben speichern</button>
|
||||||
|
|
||||||
<div class="gallery-section-label">Meine Bilder</div>
|
<div class="gallery-section-label">Meine Bilder</div>
|
||||||
<div class="gallery-upload-row">
|
<div class="gallery-upload-row">
|
||||||
<input type="file" id="galleryFile" accept="image/*" multiple style="display:none;" onchange="handleGalleryUpload(this.files)">
|
<input type="file" id="galleryFile" accept="image/*" multiple style="display:none;" onchange="handleGalleryUpload(this.files)">
|
||||||
@@ -346,7 +385,7 @@
|
|||||||
|
|
||||||
<script src="/js/shared.js"></script>
|
<script src="/js/shared.js"></script>
|
||||||
<script src="/js/image-viewer.js"></script>
|
<script src="/js/image-viewer.js"></script>
|
||||||
<script src="/js/icons.js"></script>
|
<script src="/js/icons.js"></script>
|
||||||
<script src="/js/sidebar.js"></script>
|
<script src="/js/sidebar.js"></script>
|
||||||
<script>
|
<script>
|
||||||
let currentPicture = null;
|
let currentPicture = null;
|
||||||
@@ -384,6 +423,7 @@
|
|||||||
}
|
}
|
||||||
myUserId = user.userId;
|
myUserId = user.userId;
|
||||||
loadOwnGallery();
|
loadOwnGallery();
|
||||||
|
loadVorliebenEdit();
|
||||||
})
|
})
|
||||||
.catch(() => { window.location.href = '/login.html'; });
|
.catch(() => { window.location.href = '/login.html'; });
|
||||||
|
|
||||||
@@ -596,6 +636,131 @@
|
|||||||
function hideMessage() {
|
function hideMessage() {
|
||||||
document.getElementById('message').style.display = 'none';
|
document.getElementById('message').style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Vorlieben ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const VL_SMILEYS = [
|
||||||
|
{ value: 'GEHT_GAR_NICHT', img: '/img/vorlieben/verynegative.png', color: '#e53935', title: 'Geht gar nicht' },
|
||||||
|
{ value: 'EHER_NICHT', img: '/img/vorlieben/negative.png', color: '#fb8c00', title: 'Eher nicht' },
|
||||||
|
{ value: 'NEUTRAL', img: '/img/vorlieben/neutral.png', color: '#fdd835', title: 'Neutral' },
|
||||||
|
{ value: 'MAG_ICH', img: '/img/vorlieben/positiv.png', color: '#81c784', title: 'Mag ich' },
|
||||||
|
{ value: 'UNBEDINGT', img: '/img/vorlieben/verypositiv.png', color: '#2e7d32', title: 'Unbedingt' },
|
||||||
|
{ value: 'WILL_AUSPROBIEREN', img: '/img/vorlieben/dunno.png', color: '#1e88e5', title: 'Will ich ausprobieren' },
|
||||||
|
];
|
||||||
|
|
||||||
|
let vorliebenRatings = {};
|
||||||
|
|
||||||
|
async function loadVorliebenEdit() {
|
||||||
|
const container = document.getElementById('vorliebenSection');
|
||||||
|
try {
|
||||||
|
const [itemsRes, meRes] = await Promise.all([
|
||||||
|
fetch('/vorlieben/items'),
|
||||||
|
fetch('/vorlieben/me'),
|
||||||
|
]);
|
||||||
|
if (!itemsRes.ok) {
|
||||||
|
container.innerHTML = '<p style="color:var(--color-muted);font-size:0.85rem;">Keine Vorlieben konfiguriert.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const kategorien = await itemsRes.json();
|
||||||
|
vorliebenRatings = meRes.ok ? await meRes.json() : {};
|
||||||
|
|
||||||
|
if (!kategorien.length) {
|
||||||
|
container.innerHTML = '<p style="color:var(--color-muted);font-size:0.85rem;">Keine Vorlieben konfiguriert.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const smileysHtml = VL_SMILEYS.map(s =>
|
||||||
|
`<span class="vl-smiley" data-val="${s.value}" title="${s.title}" style="--vl-color:${s.color}"><img src="${s.img}" alt="${s.title}"></span>`
|
||||||
|
).join('');
|
||||||
|
|
||||||
|
// Tab buttons
|
||||||
|
const tabBtns = kategorien.map((kat, i) =>
|
||||||
|
`<button class="vl-tab-btn${i === 0 ? ' active' : ''}" onclick="switchVlTab('vlkat-${kat.kategorieId}', this)">${escapeHtml(kat.name)}</button>`
|
||||||
|
).join('');
|
||||||
|
|
||||||
|
// Tab panels
|
||||||
|
const tabPanels = kategorien.map((kat, i) => `
|
||||||
|
<div class="vl-tab-panel${i === 0 ? ' active' : ''}" id="vlkat-${kat.kategorieId}">
|
||||||
|
${kat.items.map(item => `
|
||||||
|
<div class="vorliebe-row">
|
||||||
|
<span class="vorliebe-row-name">${escapeHtml(item.name)}</span>
|
||||||
|
<div class="vorliebe-smileys" data-item-id="${item.itemId}">
|
||||||
|
${smileysHtml}
|
||||||
|
</div>
|
||||||
|
</div>`).join('')}
|
||||||
|
</div>`).join('');
|
||||||
|
|
||||||
|
container.innerHTML = `<div class="vl-tabs">${tabBtns}</div>${tabPanels}`;
|
||||||
|
|
||||||
|
// Mark saved values
|
||||||
|
container.querySelectorAll('.vorliebe-smileys[data-item-id]').forEach(group => {
|
||||||
|
const saved = vorliebenRatings[group.dataset.itemId];
|
||||||
|
if (saved) {
|
||||||
|
const btn = group.querySelector(`.vl-smiley[data-val="${saved}"]`);
|
||||||
|
if (btn) btn.classList.add('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Click handler
|
||||||
|
container.addEventListener('click', e => {
|
||||||
|
const btn = e.target.closest('.vl-smiley');
|
||||||
|
if (!btn) return;
|
||||||
|
const group = btn.closest('.vorliebe-smileys');
|
||||||
|
const itemId = group.dataset.itemId;
|
||||||
|
const isActive = btn.classList.contains('active');
|
||||||
|
// Deselect all in this group
|
||||||
|
group.querySelectorAll('.vl-smiley').forEach(s => s.classList.remove('active'));
|
||||||
|
if (!isActive) {
|
||||||
|
btn.classList.add('active');
|
||||||
|
vorliebenRatings[itemId] = btn.dataset.val;
|
||||||
|
} else {
|
||||||
|
delete vorliebenRatings[itemId];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
container.innerHTML = '<p style="color:var(--color-muted);font-size:0.85rem;">Fehler beim Laden.</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchVlTab(panelId, btn) {
|
||||||
|
document.querySelectorAll('.vl-tab-btn').forEach(b => b.classList.remove('active'));
|
||||||
|
document.querySelectorAll('.vl-tab-panel').forEach(p => p.classList.remove('active'));
|
||||||
|
btn.classList.add('active');
|
||||||
|
document.getElementById(panelId).classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveVorlieben() {
|
||||||
|
const btn = document.getElementById('saveVorliebenBtn');
|
||||||
|
const msgEl = document.getElementById('vorliebenMessage');
|
||||||
|
btn.disabled = true; btn.textContent = 'Wird gespeichert…';
|
||||||
|
|
||||||
|
// Collect: all known items → current rating or null
|
||||||
|
const ratings = {};
|
||||||
|
document.querySelectorAll('#vorliebenSection .vorliebe-smileys[data-item-id]').forEach(group => {
|
||||||
|
const active = group.querySelector('.vl-smiley.active');
|
||||||
|
ratings[group.dataset.itemId] = active ? active.dataset.val : null;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/vorlieben/me', {
|
||||||
|
method: 'PUT', headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(ratings),
|
||||||
|
});
|
||||||
|
msgEl.textContent = res.ok ? 'Vorlieben gespeichert.' : 'Fehler beim Speichern.';
|
||||||
|
msgEl.className = `message ${res.ok ? 'success' : 'error'}`;
|
||||||
|
msgEl.style.display = 'block';
|
||||||
|
setTimeout(() => { msgEl.style.display = 'none'; }, 3000);
|
||||||
|
} catch (e) {
|
||||||
|
msgEl.textContent = 'Fehler beim Speichern.'; msgEl.className = 'message error'; msgEl.style.display = 'block';
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false; btn.textContent = 'Vorlieben speichern';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(str) {
|
||||||
|
return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||