BDSM Game umgesetzt, Community Features ergänzt

This commit is contained in:
2026-03-03 23:18:35 +01:00
parent abf85f66e4
commit 21c276e96f
140 changed files with 9552 additions and 2841 deletions

View File

@@ -1,5 +1,5 @@
#Mon Mar 02 07:06:09 CET 2026
#Tue Mar 03 07:39:34 CET 2026
display=\:0
host=Mario-Linux
process-id=8641
process-id=8529
user=mario

View File

@@ -588,3 +588,140 @@ java.lang.InterruptedException
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)
!SESSION 2026-03-02 18:52:12.395 -----------------------------------------------
eclipse.buildId=4.38.0.20251204-0849
java.version=21.0.9
java.vendor=Eclipse Adoptium
BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=de_DE
Framework arguments: -product org.eclipse.epp.package.java.product
Command-line arguments: -os linux -ws gtk -arch x86_64 -product org.eclipse.epp.package.java.product
!ENTRY ch.qos.logback.classic 1 0 2026-03-02 18:52:17.211
!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized.
!ENTRY ch.qos.logback.classic 1 0 2026-03-02 18:52:30.891
!MESSAGE Logback config file: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.m2e.logback/logback.2.7.101.20251017-1242.xml
!ENTRY org.eclipse.ui 2 0 2026-03-02 18:52:31.083
!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-02 18:52:31.083
!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
!ENTRY org.eclipse.ui 2 0 2026-03-02 18:52:31.215
!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-02 18:52:31.215
!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
!ENTRY org.eclipse.jface 2 0 2026-03-02 19:47:48.567
!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation.
!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-02 19:47:48.567
!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.debug.core 4 125 2026-03-02 20:36:48.515
!MESSAGE Error logged from Debug Core:
!STACK 0
java.io.IOException: Stream closed
at java.base/java.io.BufferedInputStream.getBufIfOpen(BufferedInputStream.java:188)
at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:343)
at java.base/java.io.BufferedInputStream.implRead(BufferedInputStream.java:420)
at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:405)
at java.base/java.io.FilterInputStream.read(FilterInputStream.java:95)
at org.eclipse.debug.internal.core.OutputStreamMonitor.internalRead(OutputStreamMonitor.java:235)
at org.eclipse.debug.internal.core.OutputStreamMonitor.read(OutputStreamMonitor.java:211)
at java.base/java.lang.Thread.run(Thread.java:1583)
!SESSION 2026-03-03 07:39:25.427 -----------------------------------------------
eclipse.buildId=4.38.0.20251204-0849
java.version=21.0.9
java.vendor=Eclipse Adoptium
BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=de_DE
Framework arguments: -product org.eclipse.epp.package.java.product
Command-line arguments: -os linux -ws gtk -arch x86_64 -product org.eclipse.epp.package.java.product
!ENTRY ch.qos.logback.classic 1 0 2026-03-03 07:39:30.215
!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized.
!ENTRY ch.qos.logback.classic 1 0 2026-03-03 07:39:35.273
!MESSAGE Logback config file: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.m2e.logback/logback.2.7.101.20251017-1242.xml
!ENTRY org.eclipse.ui 2 0 2026-03-03 07:39:35.456
!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-03 07:39:35.456
!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-03 07:39:35.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-03 07:39:35.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-03 07:56:04.643
!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation.
!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-03 07:56:04.643
!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-03 15:57:21.578
!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.lsp4e 2 0 2026-03-03 15:59:16.948
!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.lsp4e 2 0 2026-03-03 16:03:19.413
!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)

View File

@@ -1,24 +1,7 @@
[ {
"version" : "9.5.0-20260301003351+0000",
"buildTime" : "20260301003351+0000",
"commitId" : "f5df27307ea67cc4fa298aa1a9e1fc01c77458d2",
"current" : false,
"snapshot" : true,
"nightly" : true,
"releaseNightly" : false,
"activeRc" : false,
"rcFor" : "",
"milestoneFor" : "",
"broken" : false,
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260301003351+0000-bin.zip",
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260301003351+0000-bin.zip.sha256",
"checksum" : "ab9b82bcdaca040e4172b6fa10e3fcd3debaa377315760f6035c29d7837fad30",
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260301003351+0000-wrapper.jar.sha256",
"wrapperChecksum" : "7ef3d73bd95c047814d76ec8324f72deefb96593eb9ce87aa06ecdcdaba7ffe8"
}, {
"version" : "9.4.0-20260228022640+0000",
"buildTime" : "20260228022640+0000",
"commitId" : "49c4c929c0d007cf3106e4ab65e91c9591376ac8",
"version" : "9.4.0-20260302013914+0000",
"buildTime" : "20260302013914+0000",
"commitId" : "3c885266535b1a7076dbecff4aac4830bd85a74b",
"current" : false,
"snapshot" : true,
"nightly" : false,
@@ -27,11 +10,28 @@
"rcFor" : "",
"milestoneFor" : "",
"broken" : false,
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.4.0-20260228022640+0000-bin.zip",
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.4.0-20260228022640+0000-bin.zip.sha256",
"checksum" : "c8fe5e81a89bb9d72aa318f351af7f8cd6e205cb92708775082ea3b4be6e3e18",
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.4.0-20260228022640+0000-wrapper.jar.sha256",
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.4.0-20260302013914+0000-bin.zip",
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.4.0-20260302013914+0000-bin.zip.sha256",
"checksum" : "1e225dd94668c5a4d5889c63d8a7e6d48b9750b68cc24d22afe407d56583fc5f",
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.4.0-20260302013914+0000-wrapper.jar.sha256",
"wrapperChecksum" : "55243ef57851f12b070ad14f7f5bb8302daceeebc5bce5ece5fa6edb23e1145c"
}, {
"version" : "9.5.0-20260302000223+0000",
"buildTime" : "20260302000223+0000",
"commitId" : "95405f4d8f3894f368fd90697eebd275f3d9a22d",
"current" : false,
"snapshot" : true,
"nightly" : true,
"releaseNightly" : false,
"activeRc" : false,
"rcFor" : "",
"milestoneFor" : "",
"broken" : false,
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260302000223+0000-bin.zip",
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260302000223+0000-bin.zip.sha256",
"checksum" : "434486e384ac04fbe16ae2bda2f4c9373cfe9f620aff4f5cc9c51f06e6f001ab",
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260302000223+0000-wrapper.jar.sha256",
"wrapperChecksum" : "7ef3d73bd95c047814d76ec8324f72deefb96593eb9ce87aa06ecdcdaba7ffe8"
}, {
"version" : "9.4.0-rc-2",
"buildTime" : "20260227092055+0000",

File diff suppressed because one or more lines are too long

View File

@@ -8,4 +8,9 @@
<item key="DIALOG_HEIGHT" value="640"/>
<item key="DIALOG_FONT_NAME" value="1|Ubuntu Sans|11.0|0|GTK|1|"/>
</section>
<section name="org.eclipse.debug.ui.SELECT_LAUNCH_SHORTCUT_DIALOG">
<item key="DIALOG_WIDTH" value="295"/>
<item key="DIALOG_HEIGHT" value="419"/>
<item key="DIALOG_FONT_NAME" value="1|Ubuntu Sans|11.0|0|GTK|1|"/>
</section>
</section>

File diff suppressed because one or more lines are too long

View File

@@ -1,61 +1,61 @@
INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core
690321491.index
2318770678.index
1872440599.index
2240786275.index
4150628576.index
2655170954.index
4195864863.index
2982788279.index
2609856074.index
2626965509.index
2609856074.index
2769879155.index
4134502745.index
2817101718.index
4158338144.index
519552992.index
2181028596.index
2503368578.index
2181028596.index
1453089870.index
2593736024.index
721517855.index
815902026.index
3718169413.index
721517855.index
96642630.index
2488355463.index
1446719945.index
2891161224.index
1118739196.index
2891161224.index
2047888269.index
3972616808.index
2390245932.index
1205982295.index
1914043487.index
808711116.index
3154281632.index
2390245932.index
3972616808.index
808711116.index
2191830568.index
1653061733.index
2586591901.index
2609698604.index
3882180612.index
3758865325.index
2070370209.index
2332037983.index
2070370209.index
1732769785.index
2838468603.index
1436262503.index
2668411497.index
3662169204.index
2927822381.index
2398089967.index
225562445.index
1436262503.index
3662169204.index
1295630681.index
3135354350.index
3602551868.index
363836152.index
504781245.index
363836152.index
2633787677.index
1455171009.index
2725629017.index
1455171009.index
3552156823.index
4123041097.index
1865797976.index
@@ -101,5 +101,5 @@ INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.ec
198314732.index
1324521365.index
1633924572.index
2318770678.index
690321491.index
1256436118.index

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<typeInfoHistroy>
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/&lt;de.oaa.xxx.registration{RegistrationController.java[RegistrationController" modifiers="1" timestamp="1772398379804"/>
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/&lt;de.oaa.xxx.registration{RegistrationController.java[RegistrationController" modifiers="1" timestamp="1772478938560"/>
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/&lt;de.oaa.xxx.session.controller{SperreController.java[SperreController" modifiers="1" timestamp="1772390921746"/>
</typeInfoHistroy>

View File

@@ -2,3 +2,5 @@
2026-03-01 18:42:13,387 [Worker-7: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
2026-03-01 19:35:44,100 [Worker-7: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
2026-03-02 07:06:13,818 [Worker-6: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
2026-03-02 18:52:34,442 [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-03 07:39:38,191 [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.

View File

@@ -1,3 +1,3 @@
#Mon Mar 02 07:06:09 CET 2026
#Tue Mar 03 07:39:34 CET 2026
org.eclipse.core.runtime=2
org.eclipse.platform=4.38.0.v20251201-0920

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
bilder/Vorlagen/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1015 KiB

BIN
bilder/Vorlagen/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

BIN
bilder/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

BIN
bilder/icon.xcf Normal file

Binary file not shown.

BIN
bilder/icon_transparent.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

BIN
bilder/icon_transparent.xcf Normal file

Binary file not shown.

BIN
bilder/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 KiB

BIN
bilder/logo.xcf Normal file

Binary file not shown.

BIN
bilder/logo_transparent.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 KiB

BIN
bilder/logo_transparent.xcf Normal file

Binary file not shown.

BIN
bilder/lvl1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
bilder/lvl1.xcf Normal file

Binary file not shown.

BIN
bilder/lvl2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
bilder/lvl2.xcf Normal file

Binary file not shown.

BIN
bilder/lvl3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
bilder/lvl3.xcf Normal file

Binary file not shown.

BIN
bilder/lvl4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
bilder/lvl4.xcf Normal file

Binary file not shown.

BIN
bilder/lvl5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
bilder/lvl5.xcf Normal file

Binary file not shown.

View File

@@ -14,6 +14,7 @@ public class AufgabenGruppe {
private List<Aufgabe> aufgaben;
private List<Strafe> strafen;
private List<Sperre> sperren;
private List<Finisher> finisher;
private String bild;
private long subscriberCount;
private boolean subscribed;
@@ -45,6 +46,9 @@ public class AufgabenGruppe {
public List<Sperre> getSperren() { return sperren; }
public void setSperren(List<Sperre> sperren) { this.sperren = sperren; }
public List<Finisher> getFinisher() { return finisher; }
public void setFinisher(List<Finisher> finisher) { this.finisher = finisher; }
public String getBild() { return bild; }
public void setBild(String bild) { this.bild = bild; }

View File

@@ -0,0 +1,47 @@
package de.oaa.xxx.aufgaben;
import de.oaa.xxx.session.GeschlechtEnum;
import java.util.List;
import java.util.UUID;
public class Finisher {
private UUID finisherId;
private String kurzText;
private String text;
private GeschlechtEnum geschlecht;
private List<Werkzeug> benoetigtAktiv;
private List<Werkzeug> benoetigtPassiv;
private List<Toy> benoetigteToys;
private UUID gruppeId;
public UUID getFinisherId() { return finisherId; }
public void setFinisherId(UUID finisherId) { this.finisherId = finisherId; }
public String getKurzText() { return kurzText; }
public void setKurzText(String kurzText) { this.kurzText = kurzText; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
public GeschlechtEnum getGeschlecht() { return geschlecht; }
public void setGeschlecht(GeschlechtEnum geschlecht) { this.geschlecht = geschlecht; }
public List<Werkzeug> getBenoetigtAktiv() { return benoetigtAktiv; }
public void setBenoetigtAktiv(List<Werkzeug> benoetigtAktiv) { this.benoetigtAktiv = benoetigtAktiv; }
public List<Werkzeug> getBenoetigtPassiv() { return benoetigtPassiv; }
public void setBenoetigtPassiv(List<Werkzeug> benoetigtPassiv) { this.benoetigtPassiv = benoetigtPassiv; }
public List<Toy> getBenoetigteToys() { return benoetigteToys; }
public void setBenoetigteToys(List<Toy> benoetigteToys) { this.benoetigteToys = benoetigteToys; }
public UUID getGruppeId() { return gruppeId; }
public void setGruppeId(UUID gruppeId) { this.gruppeId = gruppeId; }
@Override
public String toString() {
return "Finisher[id=" + finisherId + ", kurzText=" + kurzText + ", geschlecht=" + geschlecht + ", gruppeId=" + gruppeId + "]";
}
}

View File

@@ -5,11 +5,13 @@ import de.oaa.xxx.aufgaben.AufgabenGruppeList;
import de.oaa.xxx.aufgaben.AufgabenGruppePage;
import de.oaa.xxx.aufgaben.entity.AufgabeEntity;
import de.oaa.xxx.aufgaben.entity.AufgabenGruppeEntity;
import de.oaa.xxx.aufgaben.entity.FinisherEntity;
import de.oaa.xxx.aufgaben.entity.SperreEntity;
import de.oaa.xxx.aufgaben.entity.StrafeEntity;
import de.oaa.xxx.aufgaben.entity.ToyEntity;
import de.oaa.xxx.aufgaben.repository.AufgabeRepository;
import de.oaa.xxx.aufgaben.repository.AufgabenGruppeRepository;
import de.oaa.xxx.aufgaben.repository.FinisherRepository;
import de.oaa.xxx.aufgaben.repository.GruppenAboRepository;
import de.oaa.xxx.aufgaben.repository.SperreRepository;
import de.oaa.xxx.aufgaben.repository.StrafeRepository;
@@ -57,6 +59,7 @@ public class AufgabenGruppeController {
private final AufgabeRepository aufgabeRepository;
private final StrafeRepository strafeRepository;
private final SperreRepository sperreRepository;
private final FinisherRepository finisherRepository;
private final UserRepository userRepository;
private final GruppenAboRepository aboRepository;
private final ToyRepository toyRepository;
@@ -65,6 +68,7 @@ public class AufgabenGruppeController {
AufgabeRepository aufgabeRepository,
StrafeRepository strafeRepository,
SperreRepository sperreRepository,
FinisherRepository finisherRepository,
UserRepository userRepository,
GruppenAboRepository aboRepository,
ToyRepository toyRepository) {
@@ -72,6 +76,7 @@ public class AufgabenGruppeController {
this.aufgabeRepository = aufgabeRepository;
this.strafeRepository = strafeRepository;
this.sperreRepository = sperreRepository;
this.finisherRepository = finisherRepository;
this.userRepository = userRepository;
this.aboRepository = aboRepository;
this.toyRepository = toyRepository;
@@ -198,6 +203,7 @@ public class AufgabenGruppeController {
source.getAufgaben().forEach(a -> { if (a.getBenoetigteToys() != null) allSourceToys.addAll(a.getBenoetigteToys()); });
source.getStrafen().forEach(s -> { if (s.getBenoetigteToys() != null) allSourceToys.addAll(s.getBenoetigteToys()); });
source.getSperren().forEach(sp -> { if (sp.getBenoetigteToys() != null) allSourceToys.addAll(sp.getBenoetigteToys()); });
source.getFinisher().forEach(f -> { if (f.getBenoetigteToys() != null) allSourceToys.addAll(f.getBenoetigteToys()); });
Map<UUID, ToyEntity> toyMapping = new HashMap<>();
for (ToyEntity sourceToy : allSourceToys) {
@@ -274,6 +280,19 @@ public class AufgabenGruppeController {
sperreRepository.save(spc);
}
for (FinisherEntity f : source.getFinisher()) {
FinisherEntity fc = new FinisherEntity();
fc.setFinisherId(UUID.randomUUID());
fc.setAufgabenGruppe(copy);
fc.setKurzText(f.getKurzText());
fc.setText(f.getText());
fc.setGeschlecht(f.getGeschlecht());
fc.setBenoetigtAktiv(f.getBenoetigtAktiv() != null ? new ArrayList<>(f.getBenoetigtAktiv()) : null);
fc.setBenoetigtPassiv(f.getBenoetigtPassiv() != null ? new ArrayList<>(f.getBenoetigtPassiv()) : null);
fc.setBenoetigteToys(mapToys(f.getBenoetigteToys(), toyMapping));
finisherRepository.save(fc);
}
return ResponseEntity.status(201).build();
}
@@ -298,6 +317,7 @@ public class AufgabenGruppeController {
aufgabeRepository.deleteAll(entity.getAufgaben());
strafeRepository.deleteAll(entity.getStrafen());
sperreRepository.deleteAll(entity.getSperren());
finisherRepository.deleteAll(entity.getFinisher());
gruppeRepository.delete(entity);
return ResponseEntity.accepted().build();
} catch (Exception e) {

View File

@@ -0,0 +1,113 @@
package de.oaa.xxx.aufgaben.controller;
import de.oaa.xxx.aufgaben.Finisher;
import de.oaa.xxx.aufgaben.Toy;
import de.oaa.xxx.aufgaben.entity.AufgabenGruppeEntity;
import de.oaa.xxx.aufgaben.entity.FinisherEntity;
import de.oaa.xxx.aufgaben.entity.ToyEntity;
import de.oaa.xxx.aufgaben.repository.AufgabenGruppeRepository;
import de.oaa.xxx.aufgaben.repository.FinisherRepository;
import de.oaa.xxx.aufgaben.repository.ToyRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/finisher")
@Transactional
public class FinisherController {
private static final Logger LOGGER = LoggerFactory.getLogger(FinisherController.class);
private final FinisherRepository finisherRepository;
private final AufgabenGruppeRepository gruppeRepository;
private final ToyRepository toyRepository;
public FinisherController(FinisherRepository finisherRepository,
AufgabenGruppeRepository gruppeRepository,
ToyRepository toyRepository) {
this.finisherRepository = finisherRepository;
this.gruppeRepository = gruppeRepository;
this.toyRepository = toyRepository;
}
@GetMapping("/{finisherId}")
public ResponseEntity<Finisher> get(@PathVariable UUID finisherId) {
return finisherRepository.findById(finisherId)
.map(entity -> ResponseEntity.ok(entity.toFinisher()))
.orElse(ResponseEntity.noContent().build());
}
@PostMapping
public ResponseEntity<Void> create(@RequestBody Finisher finisher) {
if (finisher.getKurzText() == null || finisher.getText() == null
|| finisher.getGeschlecht() == null || finisher.getGruppeId() == null) {
return ResponseEntity.badRequest().build();
}
AufgabenGruppeEntity gruppeEntity = gruppeRepository.findById(finisher.getGruppeId()).orElse(null);
if (gruppeEntity == null) {
return ResponseEntity.badRequest().build();
}
if (gruppeEntity.getFinisher().size() >= 100) {
return ResponseEntity.status(409).build();
}
List<ToyEntity> toys = resolveToys(finisher.getBenoetigteToys());
FinisherEntity entity = FinisherEntity.create(finisher, gruppeEntity, toys);
finisherRepository.save(entity);
return ResponseEntity.created(
ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(entity.getFinisherId()).toUri()
).build();
}
@PutMapping("/{finisherId}")
public ResponseEntity<Void> update(@PathVariable UUID finisherId, @RequestBody Finisher finisher) {
if (finisher.getKurzText() == null || finisher.getText() == null || finisher.getGeschlecht() == null) {
return ResponseEntity.badRequest().build();
}
FinisherEntity entity = finisherRepository.findById(finisherId).orElse(null);
if (entity == null) return ResponseEntity.notFound().build();
entity.setKurzText(finisher.getKurzText());
entity.setText(finisher.getText());
entity.setGeschlecht(finisher.getGeschlecht());
entity.setBenoetigtAktiv(finisher.getBenoetigtAktiv());
entity.setBenoetigtPassiv(finisher.getBenoetigtPassiv());
entity.setBenoetigteToys(resolveToys(finisher.getBenoetigteToys()));
finisherRepository.save(entity);
return ResponseEntity.ok().build();
}
@DeleteMapping
public ResponseEntity<Void> delete(@RequestBody Finisher finisher) {
try {
finisherRepository.findById(finisher.getFinisherId()).ifPresent(finisherRepository::delete);
return ResponseEntity.accepted().build();
} catch (Exception exception) {
LOGGER.error(exception.getMessage(), exception);
return ResponseEntity.internalServerError().build();
}
}
private List<ToyEntity> resolveToys(List<Toy> toys) {
if (toys == null || toys.isEmpty()) return new ArrayList<>();
List<UUID> ids = toys.stream()
.filter(t -> t.getToyId() != null)
.map(Toy::getToyId)
.toList();
if (ids.isEmpty()) return new ArrayList<>();
return toyRepository.findAllById(ids);
}
}

View File

@@ -84,6 +84,12 @@ public class AufgabeEntity {
public List<ToyEntity> getBenoetigteToys() { return benoetigteToys; }
public void setBenoetigteToys(List<ToyEntity> benoetigteToys) { this.benoetigteToys = benoetigteToys; }
@Override
public String toString() {
return "AufgabeEntity[id=" + aufgabeId + ", kurzText=" + kurzText + ", level=" + level
+ ", sekunden=" + sekundenVon + "-" + sekundenBis + "]";
}
public Aufgabe toAufgabe() {
Aufgabe aufgabe = new Aufgabe();
aufgabe.setAufgabeId(aufgabeId);

View File

@@ -39,6 +39,8 @@ public class AufgabenGruppeEntity {
private List<StrafeEntity> strafen;
@OneToMany(mappedBy = "aufgabenGruppe")
private List<SperreEntity> sperren;
@OneToMany(mappedBy = "aufgabenGruppe")
private List<FinisherEntity> finisher;
public UUID getGruppenId() { return gruppenId; }
public void setGruppenId(UUID gruppenId) { this.gruppenId = gruppenId; }
@@ -70,6 +72,15 @@ public class AufgabenGruppeEntity {
public List<SperreEntity> getSperren() { return sperren; }
public void setSperren(List<SperreEntity> sperren) { this.sperren = sperren; }
public List<FinisherEntity> getFinisher() { return finisher; }
public void setFinisher(List<FinisherEntity> finisher) { this.finisher = finisher; }
@Override
public String toString() {
return "AufgabenGruppeEntity[gruppenId=" + gruppenId + ", name=" + name + ", userId=" + userId
+ ", privat=" + privateGruppe + ", von=" + von + "]";
}
public AufgabenGruppe toAufgabenGruppe() {
AufgabenGruppe gruppe = new AufgabenGruppe();
gruppe.setGruppenId(gruppenId);
@@ -82,6 +93,7 @@ public class AufgabenGruppeEntity {
gruppe.setAufgaben(aufgaben.stream().map(AufgabeEntity::toAufgabe).toList());
gruppe.setStrafen(strafen.stream().map(StrafeEntity::toStrafe).toList());
gruppe.setSperren(sperren.stream().map(SperreEntity::toSperre).toList());
gruppe.setFinisher(finisher.stream().map(FinisherEntity::toFinisher).toList());
return gruppe;
}

View File

@@ -29,6 +29,11 @@ public class FavoritEntity {
public UUID getAufgabenGruppeId() { return aufgabenGruppeId; }
public void setAufgabenGruppeId(UUID aufgabenGruppeId) { this.aufgabenGruppeId = aufgabenGruppeId; }
@Override
public String toString() {
return "FavoritEntity[favoritId=" + favoritId + ", userId=" + userId + ", gruppeId=" + aufgabenGruppeId + "]";
}
public Favorit toFavorit() {
Favorit favorit = new Favorit();
favorit.setAufgabenGruppeId(aufgabenGruppeId);

View File

@@ -0,0 +1,109 @@
package de.oaa.xxx.aufgaben.entity;
import de.oaa.xxx.aufgaben.Finisher;
import de.oaa.xxx.aufgaben.Werkzeug;
import de.oaa.xxx.session.GeschlechtEnum;
import jakarta.persistence.CascadeType;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Entity
@Table(name = "finisher")
public class FinisherEntity {
@Id
@Column
private UUID finisherId;
@Column
private String kurzText;
@Column(columnDefinition = "TEXT")
private String text;
@Enumerated(EnumType.STRING)
@Column
private GeschlechtEnum geschlecht;
@ManyToOne
@JoinColumn(name = "gruppeId")
private AufgabenGruppeEntity aufgabenGruppe;
@Enumerated(EnumType.STRING)
@ElementCollection(targetClass = Werkzeug.class)
@CollectionTable(name = "finisher_benoetigtAktiv", joinColumns = @JoinColumn(name = "finisherId"))
@Column(name = "werkzeug")
private List<Werkzeug> benoetigtAktiv;
@Enumerated(EnumType.STRING)
@ElementCollection(targetClass = Werkzeug.class)
@CollectionTable(name = "finisher_benoetigtPassiv", joinColumns = @JoinColumn(name = "finisherId"))
@Column(name = "werkzeug")
private List<Werkzeug> benoetigtPassiv;
@ManyToMany(cascade = CascadeType.DETACH)
@JoinTable(name = "finisherToy", joinColumns = {@JoinColumn(name = "finisherId")}, inverseJoinColumns = {@JoinColumn(name = "toyId")})
private List<ToyEntity> benoetigteToys;
public UUID getFinisherId() { return finisherId; }
public void setFinisherId(UUID finisherId) { this.finisherId = finisherId; }
public String getKurzText() { return kurzText; }
public void setKurzText(String kurzText) { this.kurzText = kurzText; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
public GeschlechtEnum getGeschlecht() { return geschlecht; }
public void setGeschlecht(GeschlechtEnum geschlecht) { this.geschlecht = geschlecht; }
public AufgabenGruppeEntity getAufgabenGruppe() { return aufgabenGruppe; }
public void setAufgabenGruppe(AufgabenGruppeEntity aufgabenGruppe) { this.aufgabenGruppe = aufgabenGruppe; }
public List<Werkzeug> getBenoetigtAktiv() { return benoetigtAktiv; }
public void setBenoetigtAktiv(List<Werkzeug> benoetigtAktiv) { this.benoetigtAktiv = benoetigtAktiv; }
public List<Werkzeug> getBenoetigtPassiv() { return benoetigtPassiv; }
public void setBenoetigtPassiv(List<Werkzeug> benoetigtPassiv) { this.benoetigtPassiv = benoetigtPassiv; }
public List<ToyEntity> getBenoetigteToys() { return benoetigteToys; }
public void setBenoetigteToys(List<ToyEntity> benoetigteToys) { this.benoetigteToys = benoetigteToys; }
@Override
public String toString() {
return "FinisherEntity[id=" + finisherId + ", kurzText=" + kurzText + ", geschlecht=" + geschlecht + "]";
}
public Finisher toFinisher() {
Finisher finisher = new Finisher();
finisher.setFinisherId(finisherId);
finisher.setKurzText(kurzText);
finisher.setText(text);
finisher.setGeschlecht(geschlecht);
finisher.setBenoetigtAktiv(benoetigtAktiv);
finisher.setBenoetigtPassiv(benoetigtPassiv);
finisher.setBenoetigteToys(benoetigteToys.stream().map(ToyEntity::toToy).toList());
finisher.setGruppeId(aufgabenGruppe.getGruppenId());
return finisher;
}
public static FinisherEntity create(Finisher finisher, AufgabenGruppeEntity aufgabenGruppeEntity, List<ToyEntity> toys) {
FinisherEntity entity = new FinisherEntity();
entity.setFinisherId(UUID.randomUUID());
entity.setAufgabenGruppe(aufgabenGruppeEntity);
entity.setKurzText(finisher.getKurzText());
entity.setText(finisher.getText());
entity.setGeschlecht(finisher.getGeschlecht());
entity.setBenoetigtAktiv(finisher.getBenoetigtAktiv());
entity.setBenoetigtPassiv(finisher.getBenoetigtPassiv());
entity.setBenoetigteToys(toys != null ? toys : new ArrayList<>());
return entity;
}
}

View File

@@ -32,4 +32,10 @@ public class GruppenAboEntity {
public AufgabenGruppeEntity getAufgabenGruppe() { return aufgabenGruppe; }
public void setAufgabenGruppe(AufgabenGruppeEntity aufgabenGruppe) { this.aufgabenGruppe = aufgabenGruppe; }
@Override
public String toString() {
return "GruppenAboEntity[aboId=" + aboId + ", userId=" + userId
+ ", gruppe=" + (aufgabenGruppe != null ? aufgabenGruppe.getName() : null) + "]";
}
}

View File

@@ -76,6 +76,12 @@ public class SperreEntity {
public List<ToyEntity> getBenoetigteToys() { return benoetigteToys; }
public void setBenoetigteToys(List<ToyEntity> benoetigteToys) { this.benoetigteToys = benoetigteToys; }
@Override
public String toString() {
return "SperreEntity[id=" + sperreId + ", kurzText=" + kurzText
+ ", minuten=" + minutenVon + "-" + minutenBis + ", fuer=" + sperreFuer + "]";
}
public Sperre toSperre() {
Sperre sperre = new Sperre();
sperre.setSperreId(sperreId);

View File

@@ -84,6 +84,12 @@ public class StrafeEntity {
public List<ToyEntity> getBenoetigteToys() { return benoetigteToys; }
public void setBenoetigteToys(List<ToyEntity> benoetigteToys) { this.benoetigteToys = benoetigteToys; }
@Override
public String toString() {
return "StrafeEntity[id=" + strafeId + ", kurzText=" + kurzText + ", level=" + level
+ ", sekunden=" + sekundenVon + "-" + sekundenBis + "]";
}
public Strafe toStrafe() {
Strafe strafe = new Strafe();
strafe.setStrafeId(strafeId);

View File

@@ -42,6 +42,11 @@ public class ToyEntity {
public byte[] getBild() { return bild; }
public void setBild(byte[] bild) { this.bild = bild; }
@Override
public String toString() {
return "ToyEntity[toyId=" + toyId + ", name=" + name + ", userId=" + userId + "]";
}
public Toy toToy() {
Toy toy = new Toy();
toy.setToyId(toyId);

View File

@@ -1,9 +1,13 @@
package de.oaa.xxx.aufgaben.repository;
import de.oaa.xxx.aufgaben.entity.AufgabeEntity;
import de.oaa.xxx.aufgaben.entity.AufgabenGruppeEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.UUID;
public interface AufgabeRepository extends JpaRepository<AufgabeEntity, UUID> {
List<AufgabeEntity> findByAufgabenGruppeIn(List<AufgabenGruppeEntity> gruppen);
}

View File

@@ -11,4 +11,6 @@ public interface FavoritRepository extends JpaRepository<FavoritEntity, UUID> {
List<FavoritEntity> findByUserId(UUID userId);
List<FavoritEntity> findByUserIdAndAufgabenGruppeId(UUID userId, UUID aufgabenGruppeId);
void deleteByAufgabenGruppeId(UUID aufgabenGruppeId);
}

View File

@@ -0,0 +1,9 @@
package de.oaa.xxx.aufgaben.repository;
import de.oaa.xxx.aufgaben.entity.FinisherEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.UUID;
public interface FinisherRepository extends JpaRepository<FinisherEntity, UUID> {
}

View File

@@ -1,9 +1,13 @@
package de.oaa.xxx.aufgaben.repository;
import de.oaa.xxx.aufgaben.entity.AufgabenGruppeEntity;
import de.oaa.xxx.aufgaben.entity.SperreEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.UUID;
public interface SperreRepository extends JpaRepository<SperreEntity, UUID> {
List<SperreEntity> findByAufgabenGruppeIn(List<AufgabenGruppeEntity> gruppen);
}

View File

@@ -1,9 +1,13 @@
package de.oaa.xxx.aufgaben.repository;
import de.oaa.xxx.aufgaben.entity.AufgabenGruppeEntity;
import de.oaa.xxx.aufgaben.entity.StrafeEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.UUID;
public interface StrafeRepository extends JpaRepository<StrafeEntity, UUID> {
List<StrafeEntity> findByAufgabenGruppeIn(List<AufgabenGruppeEntity> gruppen);
}

View File

@@ -7,6 +7,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@@ -16,6 +17,8 @@ public interface ToyRepository extends JpaRepository<ToyEntity, UUID> {
Page<ToyEntity> findByUserId(UUID userId, Pageable pageable);
List<ToyEntity> findByUserId(UUID userId);
boolean existsByNameIgnoreCaseAndUserIdIsNull(String name);
boolean existsByNameIgnoreCaseAndUserId(String name, UUID userId);

View File

@@ -36,18 +36,40 @@ public class SecurityConfig {
.requestMatchers(AntPathRequestMatcher.antMatcher("/toys.html")).authenticated()
.requestMatchers(AntPathRequestMatcher.antMatcher("/aufgaben.html")).authenticated()
.requestMatchers(AntPathRequestMatcher.antMatcher("/entdecken.html")).authenticated()
.requestMatchers(AntPathRequestMatcher.antMatcher("/profile.html")).authenticated()
.requestMatchers(AntPathRequestMatcher.antMatcher("/infovanilla.html")).authenticated()
.requestMatchers(AntPathRequestMatcher.antMatcher("/infobdsm.html")).authenticated()
.requestMatchers(AntPathRequestMatcher.antMatcher("/infochastity.html")).authenticated()
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionvanilla.html")).authenticated()
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionbdsm.html")).authenticated()
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionchastity.html")).authenticated()
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionbdsmtasks.html")).authenticated()
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionbdsmtoys.html")).authenticated()
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionbdsmingame.html")).authenticated()
.requestMatchers(AntPathRequestMatcher.antMatcher("/personen-suchen.html")).authenticated()
.requestMatchers(AntPathRequestMatcher.antMatcher("/freunde.html")).authenticated()
.requestMatchers(AntPathRequestMatcher.antMatcher("/nachrichten.html")).authenticated()
.requestMatchers(AntPathRequestMatcher.antMatcher("/benutzer.html")).authenticated()
.requestMatchers(AntPathRequestMatcher.antMatcher("/*.html")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/css/**")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/js/**")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/images/**")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/favicon.ico")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/*.png")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/*.jpg")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/*.svg")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/*.webp")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/login")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/login/publickey")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/login/logout")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.POST, "/user")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/registration")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.POST, "/registration")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/activation")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/activation/**")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.POST, "/password-reset/request")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.POST, "/password-reset/confirm")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/email-change/**")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.POST, "/filler")).permitAll()
.anyRequest().authenticated()
)

View File

@@ -0,0 +1,119 @@
package de.oaa.xxx.emailchange;
import de.oaa.xxx.mail.Email;
import de.oaa.xxx.mail.MailService;
import de.oaa.xxx.mail.MailTemplateService;
import de.oaa.xxx.registration.RegistrationRepository;
import de.oaa.xxx.user.UserRepository;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.security.Principal;
import java.util.UUID;
@RestController
@RequestMapping("/email-change")
public class EmailChangeController {
private static final Logger LOGGER = LoggerFactory.getLogger(EmailChangeController.class);
@Value("${app.base-url:http://localhost:8080}")
private String baseUrl;
private final EmailChangeRepository emailChangeRepository;
private final UserRepository userRepository;
private final RegistrationRepository registrationRepository;
private final MailService mailService;
private final MailTemplateService mailTemplateService;
public EmailChangeController(EmailChangeRepository emailChangeRepository,
UserRepository userRepository,
RegistrationRepository registrationRepository,
MailService mailService,
MailTemplateService mailTemplateService) {
this.emailChangeRepository = emailChangeRepository;
this.userRepository = userRepository;
this.registrationRepository = registrationRepository;
this.mailService = mailService;
this.mailTemplateService = mailTemplateService;
}
record EmailChangeRequest(String newEmail) {}
@PostMapping
public ResponseEntity<Void> requestChange(@RequestBody EmailChangeRequest request, Principal principal) {
String currentEmail = principal.getName();
String newEmail = request.newEmail();
if (userRepository.findByEmail(newEmail).isPresent()
|| registrationRepository.findByEmail(newEmail).isPresent()) {
return ResponseEntity.status(409).build();
}
// Remove any pending request for this user
emailChangeRepository.findByUserEmail(currentEmail)
.ifPresent(emailChangeRepository::delete);
var user = userRepository.findByEmail(currentEmail);
if (user.isEmpty()) return ResponseEntity.status(401).build();
EmailChangeEntity entity = EmailChangeEntity.create(currentEmail, newEmail);
emailChangeRepository.save(entity);
Email email = new Email();
email.setTitel("Bitte bestätige deine neue E-Mail-Adresse");
email.setEmailAdresse(newEmail);
String confirmLink = baseUrl + "/email-change/" + entity.getTokenId().toString();
email.setText(mailTemplateService.buildEmailChangeMail(user.get().getName(), confirmLink, newEmail));
if (!mailService.send(email)) {
emailChangeRepository.delete(entity);
return ResponseEntity.internalServerError().build();
}
return ResponseEntity.status(202).build();
}
@GetMapping("/{token}")
public void confirm(@PathVariable String token, HttpServletResponse response) throws IOException {
UUID tokenId;
try {
tokenId = UUID.fromString(token);
} catch (IllegalArgumentException e) {
response.sendRedirect("/login.html");
return;
}
var entity = emailChangeRepository.findById(tokenId);
if (entity.isEmpty()) {
response.sendRedirect("/login.html");
return;
}
var user = userRepository.findByEmail(entity.get().getUserEmail());
if (user.isPresent()) {
user.get().setEmail(entity.get().getNewEmail());
userRepository.save(user.get());
LOGGER.info("E-Mail geändert von {} zu {}", entity.get().getUserEmail(), entity.get().getNewEmail());
}
emailChangeRepository.delete(entity.get());
// Clear JWT cookie so user must log in with new email
ResponseCookie cookie = ResponseCookie.from("jwt", "")
.httpOnly(true)
.sameSite("Strict")
.path("/")
.maxAge(0)
.build();
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
response.sendRedirect("/login.html?emailChanged=1");
}
}

View File

@@ -0,0 +1,53 @@
package de.oaa.xxx.emailchange;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import java.util.UUID;
@Entity
@Table(name = "email_change")
public class EmailChangeEntity {
@Id
@Column
private UUID tokenId;
@Column
private String userEmail;
@Column
private String newEmail;
@Column
private LocalDateTime createdAt;
public UUID getTokenId() { return tokenId; }
public void setTokenId(UUID tokenId) { this.tokenId = tokenId; }
public String getUserEmail() { return userEmail; }
public void setUserEmail(String userEmail) { this.userEmail = userEmail; }
public String getNewEmail() { return newEmail; }
public void setNewEmail(String newEmail) { this.newEmail = newEmail; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
@Override
public String toString() {
return "EmailChangeEntity[tokenId=" + tokenId + ", userEmail=" + userEmail + ", newEmail=" + newEmail + ", createdAt=" + createdAt + "]";
}
public static EmailChangeEntity create(String userEmail, String newEmail) {
EmailChangeEntity entity = new EmailChangeEntity();
entity.setTokenId(UUID.randomUUID());
entity.setUserEmail(userEmail);
entity.setNewEmail(newEmail);
entity.setCreatedAt(LocalDateTime.now());
return entity;
}
}

View File

@@ -0,0 +1,11 @@
package de.oaa.xxx.emailchange;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
import java.util.UUID;
public interface EmailChangeRepository extends JpaRepository<EmailChangeEntity, UUID> {
Optional<EmailChangeEntity> findByUserEmail(String userEmail);
}

View File

@@ -32,6 +32,90 @@ public class MailTemplateService {
@Value("${app.theme.color-success:#2ecc71}")
private String colorSuccess;
public String buildEmailChangeMail(String name, String confirmLink, String newEmail) {
return """
<!DOCTYPE html>
<html lang="de">
<body style="margin:0; padding:2rem; background:%s; font-family:'Segoe UI',Arial,sans-serif; color:%s;">
<div style="max-width:460px; margin:0 auto; background:%s; border:1px solid %s; border-radius:12px; padding:2.5rem; box-shadow:0 8px 32px rgba(0,0,0,0.5);">
<h1 style="color:%s; text-align:center; margin:0 0 1.5rem 0; font-size:1.6rem;">XXX The Game</h1>
<p style="color:%s; margin:0 0 0.75rem 0;">Moin %s,</p>
<p style="color:%s; margin:0 0 0.5rem 0;">Du hast eine Änderung deiner E-Mail-Adresse angefordert.</p>
<p style="color:%s; margin:0 0 2rem 0;">Klick auf den Button, um deine neue Adresse <strong style="color:%s;">%s</strong> zu bestätigen:</p>
<div style="text-align:center; margin:0 0 2rem 0;">
<a href="%s"
style="display:inline-block; padding:0.75rem 2.5rem; background:%s; color:#ffffff;
border-radius:6px; text-decoration:none; font-weight:600; font-size:1rem;">
E-Mail-Adresse bestätigen
</a>
</div>
<hr style="border:none; border-top:1px solid %s; margin:0 0 1.5rem 0;">
<p style="color:%s; font-size:0.85em; margin:0;">
Falls du diese Änderung nicht angefordert hast, kannst du diese E-Mail einfach ignorieren.
</p>
</div>
</body>
</html>
""".formatted(
colorBg, colorText,
colorCard, colorSecondary,
colorPrimary,
colorText, name,
colorText,
colorText, colorPrimary, newEmail,
confirmLink, colorPrimary,
colorSecondary,
colorMuted
);
}
public String buildPasswordResetMail(String name, String resetLink) {
return """
<!DOCTYPE html>
<html lang="de">
<body style="margin:0; padding:2rem; background:%s; font-family:'Segoe UI',Arial,sans-serif; color:%s;">
<div style="max-width:460px; margin:0 auto; background:%s; border:1px solid %s; border-radius:12px; padding:2.5rem; box-shadow:0 8px 32px rgba(0,0,0,0.5);">
<h1 style="color:%s; text-align:center; margin:0 0 1.5rem 0; font-size:1.6rem;">XXX The Game</h1>
<p style="color:%s; margin:0 0 0.75rem 0;">Moin %s,</p>
<p style="color:%s; margin:0 0 0.5rem 0;">Du hast eine Anfrage zum Zurücksetzen deines Passworts gestellt.</p>
<p style="color:%s; margin:0 0 2rem 0;">Klick auf den Button, um ein neues Passwort zu vergeben:</p>
<div style="text-align:center; margin:0 0 2rem 0;">
<a href="%s"
style="display:inline-block; padding:0.75rem 2.5rem; background:%s; color:#ffffff;
border-radius:6px; text-decoration:none; font-weight:600; font-size:1rem;">
Passwort zurücksetzen
</a>
</div>
<hr style="border:none; border-top:1px solid %s; margin:0 0 1.5rem 0;">
<p style="color:%s; font-size:0.85em; margin:0;">
Falls du diese Anfrage nicht gestellt hast, kannst du diese E-Mail einfach ignorieren.
</p>
</div>
</body>
</html>
""".formatted(
colorBg, colorText,
colorCard, colorSecondary,
colorPrimary,
colorText, name,
colorText,
colorText,
resetLink, colorPrimary,
colorSecondary,
colorMuted
);
}
public String buildActivationMail(String name, String activationLink, String activatePageUrl, String uuid) {
return """
<!DOCTYPE html>

View File

@@ -0,0 +1,3 @@
package de.oaa.xxx.passwordreset;
public record PasswordResetConfirm(String token, String passwordHash) {}

View File

@@ -0,0 +1,77 @@
package de.oaa.xxx.passwordreset;
import de.oaa.xxx.mail.Email;
import de.oaa.xxx.mail.MailService;
import de.oaa.xxx.mail.MailTemplateService;
import de.oaa.xxx.user.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.UUID;
@RestController
@RequestMapping("/password-reset")
public class PasswordResetController {
private static final Logger LOGGER = LoggerFactory.getLogger(PasswordResetController.class);
@Value("${app.base-url:http://localhost:8080}")
private String baseUrl;
private final PasswordResetRepository passwordResetRepository;
private final UserRepository userRepository;
private final MailService mailService;
private final MailTemplateService mailTemplateService;
public PasswordResetController(PasswordResetRepository passwordResetRepository,
UserRepository userRepository,
MailService mailService,
MailTemplateService mailTemplateService) {
this.passwordResetRepository = passwordResetRepository;
this.userRepository = userRepository;
this.mailService = mailService;
this.mailTemplateService = mailTemplateService;
}
@PostMapping("/request")
public ResponseEntity<Void> request(@RequestBody PasswordResetRequest request) {
userRepository.findByEmail(request.email()).ifPresent(user -> {
passwordResetRepository.findByEmail(request.email())
.ifPresent(passwordResetRepository::delete);
PasswordResetEntity entity = PasswordResetEntity.create(request.email());
passwordResetRepository.save(entity);
String resetLink = baseUrl + "/reset-password.html?token=" + entity.getTokenId();
Email email = new Email();
email.setTitel("Passwort zurücksetzen");
email.setEmailAdresse(request.email());
email.setText(mailTemplateService.buildPasswordResetMail(user.getName(), resetLink));
mailService.send(email);
LOGGER.info("Passwort-Reset angefordert für: {}", request.email());
});
return ResponseEntity.status(202).build();
}
@PostMapping("/confirm")
public ResponseEntity<Void> confirm(@RequestBody PasswordResetConfirm confirm) {
UUID tokenId;
try {
tokenId = UUID.fromString(confirm.token());
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().build();
}
var entity = passwordResetRepository.findById(tokenId);
if (entity.isEmpty()) {
return ResponseEntity.badRequest().build();
}
userRepository.findByEmail(entity.get().getEmail()).ifPresent(user -> {
user.setPassword(confirm.passwordHash());
userRepository.save(user);
LOGGER.info("Passwort zurückgesetzt für: {}", entity.get().getEmail());
});
passwordResetRepository.delete(entity.get());
return ResponseEntity.ok().build();
}
}

View File

@@ -0,0 +1,46 @@
package de.oaa.xxx.passwordreset;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import java.util.UUID;
@Entity
@Table(name = "password_reset")
public class PasswordResetEntity {
@Id
@Column
private UUID tokenId;
@Column(unique = true)
private String email;
@Column
private LocalDateTime createdAt;
public UUID getTokenId() { return tokenId; }
public void setTokenId(UUID tokenId) { this.tokenId = tokenId; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
@Override
public String toString() {
return "PasswordResetEntity[tokenId=" + tokenId + ", email=" + email + ", createdAt=" + createdAt + "]";
}
public static PasswordResetEntity create(String email) {
PasswordResetEntity entity = new PasswordResetEntity();
entity.setTokenId(UUID.randomUUID());
entity.setEmail(email);
entity.setCreatedAt(LocalDateTime.now());
return entity;
}
}

View File

@@ -0,0 +1,10 @@
package de.oaa.xxx.passwordreset;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
import java.util.UUID;
public interface PasswordResetRepository extends JpaRepository<PasswordResetEntity, UUID> {
Optional<PasswordResetEntity> findByEmail(String email);
}

View File

@@ -0,0 +1,3 @@
package de.oaa.xxx.passwordreset;
public record PasswordResetRequest(String email) {}

View File

@@ -44,6 +44,11 @@ public class RegistrationController {
LOGGER.warn("User mit E-Mail {} bereits vorhanden", registration.getEmail());
return ResponseEntity.badRequest().build();
}
if (registrationRepository.findByName(registration.getName()).isPresent()
|| userRepository.findByName(registration.getName()).isPresent()) {
LOGGER.warn("User mit Name {} bereits vorhanden", registration.getName());
return ResponseEntity.status(409).build();
}
RegistrationEntity entity = RegistrationEntity.create(registration);
registrationRepository.save(entity);

View File

@@ -8,4 +8,5 @@ import java.util.UUID;
public interface RegistrationRepository extends JpaRepository<RegistrationEntity, UUID> {
Optional<RegistrationEntity> findByEmail(String email);
Optional<RegistrationEntity> findByName(String name);
}

View File

@@ -34,4 +34,10 @@ public class AktiveSperre {
public String getReleaseText() { return releaseText; }
public void setReleaseText(String releaseText) { this.releaseText = releaseText; }
@Override
public String toString() {
return "AktiveSperre[id=" + aktiveSperreId + ", mitspieler=" + (mitspieler != null ? mitspieler.getName() : null)
+ ", " + minuten + "min, von=" + startzeit + ", bis=" + endzeit + ", fuer=" + fuer + "]";
}
}

View File

@@ -6,6 +6,7 @@ public class AufgabeAnzeige {
private String aufgabeText;
private Integer timer;
private Callback callback;
private Integer level;
public String getNameAktiverMitspieler() { return nameAktiverMitspieler; }
public void setNameAktiverMitspieler(String nameAktiverMitspieler) { this.nameAktiverMitspieler = nameAktiverMitspieler; }
@@ -18,4 +19,13 @@ public class AufgabeAnzeige {
public Callback getCallback() { return callback; }
public void setCallback(Callback callback) { this.callback = callback; }
public Integer getLevel() { return level; }
public void setLevel(Integer level) { this.level = level; }
@Override
public String toString() {
return "AufgabeAnzeige[mitspieler=" + nameAktiverMitspieler + ", level=" + level + ", timer=" + timer
+ ", callback=" + (callback != null ? callback.getClass().getSimpleName() : null) + "]";
}
}

View File

@@ -8,4 +8,9 @@ public abstract class Callback {
public UUID getSessionId() { return sessionId; }
public void setSessionId(UUID sessionId) { this.sessionId = sessionId; }
@Override
public String toString() {
return getClass().getSimpleName() + "[sessionId=" + sessionId + "]";
}
}

View File

@@ -34,6 +34,12 @@ public class Mitspieler {
return verfuegbareWerkzeuge.contains(werkzeug);
}
@Override
public String toString() {
return "Mitspieler[id=" + id + ", name=" + name + ", geschlecht=" + geschlecht
+ ", rollen=" + rollen + ", werkzeuge=" + verfuegbareWerkzeuge + "]";
}
public boolean isPassenderSpielpartner(Mitspieler other) {
if (!spieltMit.contains(other.getGeschlecht())) {
return false;

View File

@@ -1,5 +1,6 @@
package de.oaa.xxx.session;
import java.time.LocalDateTime;
import java.util.UUID;
public class Session {
@@ -10,6 +11,10 @@ public class Session {
private Integer wahrscheinlichkeitStrafe;
private Integer aufgabenProLevel;
private Double zeitfaktorZeitstrafen;
private Integer level;
private Integer aufgabenAufAktuellemLevel;
private LocalDateTime startZeit;
private LocalDateTime letzteAktivitaet;
public UUID getSessionId() { return sessionId; }
public void setSessionId(UUID sessionId) { this.sessionId = sessionId; }
@@ -28,4 +33,24 @@ public class Session {
public Double getZeitfaktorZeitstrafen() { return zeitfaktorZeitstrafen; }
public void setZeitfaktorZeitstrafen(Double zeitfaktorZeitstrafen) { this.zeitfaktorZeitstrafen = zeitfaktorZeitstrafen; }
public Integer getLevel() { return level; }
public void setLevel(Integer level) { this.level = level; }
public Integer getAufgabenAufAktuellemLevel() { return aufgabenAufAktuellemLevel; }
public void setAufgabenAufAktuellemLevel(Integer aufgabenAufAktuellemLevel) { this.aufgabenAufAktuellemLevel = aufgabenAufAktuellemLevel; }
public LocalDateTime getStartZeit() { return startZeit; }
public void setStartZeit(LocalDateTime startZeit) { this.startZeit = startZeit; }
public LocalDateTime getLetzteAktivitaet() { return letzteAktivitaet; }
public void setLetzteAktivitaet(LocalDateTime letzteAktivitaet) { this.letzteAktivitaet = letzteAktivitaet; }
@Override
public String toString() {
return "Session[sessionId=" + sessionId + ", userId=" + userId
+ ", level=" + level + ", aufgaben=" + aufgabenAufAktuellemLevel + "/" + aufgabenProLevel
+ ", pStrafe=" + wahrscheinlichkeitStrafe + "%, pSperre=" + wahrscheinlichkeitSperre + "%"
+ ", zeitfaktor=" + zeitfaktorZeitstrafen + "]";
}
}

View File

@@ -1,6 +1,12 @@
package de.oaa.xxx.session;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.oaa.xxx.session.aufgaben.Aufgabe;
import de.oaa.xxx.session.aufgaben.AufgabenList;
import de.oaa.xxx.session.aufgaben.Sperre;
@@ -9,11 +15,6 @@ import de.oaa.xxx.session.entity.SessionEntity;
import de.oaa.xxx.session.sperre.SperreCallback;
import de.oaa.xxx.session.sperre.SperrenVerlaengernCallback;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
public class SessionDurchfuehren {
private final AufgabenList aufgabenList;
@@ -23,12 +24,12 @@ public class SessionDurchfuehren {
private final Integer wahrscheinlichkeitSperre;
private final Integer wahrscheinlichkeitStrafe;
private final Integer aufgabenProLevel;
private Integer level;
private Integer aufgabenAufAktuellemLevel;
private int aufgabenProLevel;
private int level;
private int aufgabenAufAktuellemLevel;
public SessionDurchfuehren(SessionEntity entity) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
ObjectMapper objectMapper = new ObjectMapper();
aufgabenList = objectMapper.readValue(entity.getAufgaben(), AufgabenList.class);
entity.getMitspieler().forEach(mitspielerEntity -> mitspieler.add(mitspielerEntity.toMitspieler()));
entity.getAktiveSperren().forEach(sperreEntity -> aktiveSperren.add(sperreEntity.toSperre(mitspieler)));
@@ -36,13 +37,16 @@ public class SessionDurchfuehren {
wahrscheinlichkeitSperre = entity.getWahrscheinlichkeitSperre();
wahrscheinlichkeitStrafe = entity.getWahrscheinlichkeitStrafe();
aufgabenProLevel = entity.getAufgabenProLevel() != null ? entity.getAufgabenProLevel() : 5;
level = entity.getLevel() != null ? entity.getLevel() : 1;
aufgabenAufAktuellemLevel = entity.getAufgabenAufAktuellemLevel() != null ? entity.getAufgabenAufAktuellemLevel() : 0;
this.aufgabenProLevel = entity.getAufgabenProLevel() != null ? entity.getAufgabenProLevel() : 5;
this.level = entity.getLevel() != null ? entity.getLevel() : 1;
this.aufgabenAufAktuellemLevel = entity.getAufgabenAufAktuellemLevel() != null ? entity.getAufgabenAufAktuellemLevel() : 0;
}
public AufgabeAnzeige getNext() {
checkLevel();
if (level == 6) {
return null;
}
AufgabeAnzeige anzeige = null;
int nextInt = new Random().nextInt(1, 100);
if (nextInt == 1) {
@@ -67,9 +71,34 @@ public class SessionDurchfuehren {
}
return anzeige;
}
public void backToLvl5() {
this.level = 5;
this.aufgabenAufAktuellemLevel = 0;
}
public List<AufgabeAnzeige> getFinisher() {
var list = new ArrayList<AufgabeAnzeige>();
List.of(GeschlechtEnum.WEIBLICH, GeschlechtEnum.DIVERS, GeschlechtEnum.MAENNLICH).forEach(geschlecht -> {
mitspieler.stream().filter(m -> geschlecht == m.getGeschlecht()).toList().forEach(cumming -> {
var partner = findeMitspielerMitRolle(RolleEnum.AUFGABE_PASSIV, cumming);
aufgabenList.getFinisher().stream()
.filter(finisher -> geschlecht == finisher.getGeschlecht())
.findAny()
.ifPresent(aufgabe -> {
AufgabeAnzeige anzeige = new AufgabeAnzeige();
anzeige.setNameAktiverMitspieler(cumming.getName());
anzeige.setAufgabeText(getAnzeigeText(aufgabe.getText(),
cumming.getName(), partner != null ? partner.getName() : ""));
list.add(anzeige);
});
});
});
return list;
}
private void checkLevel() {
if (++aufgabenAufAktuellemLevel >= aufgabenProLevel && level < 5) {
if (++aufgabenAufAktuellemLevel >= 1 + aufgabenProLevel) {
aufgabenAufAktuellemLevel = 0;
level++;
}
@@ -210,4 +239,12 @@ public class SessionDurchfuehren {
.toList();
return list.isEmpty() ? null : list.get(new Random().nextInt(list.size()));
}
public int getAufgabenAufAktuellemLevel() {
return aufgabenAufAktuellemLevel;
}
public int getLevel() {
return level;
}
}

View File

@@ -45,6 +45,12 @@ public class Aufgabe {
public List<Werkzeug> getBenoetigtPassiv() { return benoetigtPassiv; }
public void setBenoetigtPassiv(List<Werkzeug> benoetigtPassiv) { this.benoetigtPassiv = benoetigtPassiv; }
@Override
public String toString() {
return "Aufgabe[id=" + aufgabeId + ", kurzText=" + kurzText + ", level=" + level
+ ", sekunden=" + sekundenVon + "-" + sekundenBis + ", gruppeId=" + gruppeId + "]";
}
public boolean isAufgabePassend(int level, Mitspieler aktiv, Mitspieler passiv) {
if (level != this.level && level - 1 != this.level) {
return false;

View File

@@ -7,6 +7,7 @@ public class AufgabenList {
private List<Aufgabe> aufgaben;
private List<Sperre> sperren;
private List<Strafe> strafen;
private List<Finisher> finisher;
public List<Aufgabe> getAufgaben() { return aufgaben; }
public void setAufgaben(List<Aufgabe> aufgaben) { this.aufgaben = aufgaben; }
@@ -17,11 +18,15 @@ public class AufgabenList {
public List<Strafe> getStrafen() { return strafen; }
public void setStrafen(List<Strafe> strafen) { this.strafen = strafen; }
public List<Finisher> getFinisher() { return finisher; }
public void setFinisher(List<Finisher> finisher) { this.finisher = finisher; }
public int size() {
int size = 0;
if (aufgaben != null) size += aufgaben.size();
if (sperren != null) size += sperren.size();
if (strafen != null) size += strafen.size();
if (getFinisher() != null) size += getFinisher().size();
return size;
}
}

View File

@@ -0,0 +1,40 @@
package de.oaa.xxx.session.aufgaben;
import java.util.List;
import java.util.UUID;
import de.oaa.xxx.session.GeschlechtEnum;
import de.oaa.xxx.session.Werkzeug;
public class Finisher {
private UUID finisherId;
private String kurzText;
private String text;
private GeschlechtEnum geschlecht;
private List<Werkzeug> benoetigtAktiv;
private List<Werkzeug> benoetigtPassiv;
public UUID getFinisherId() { return finisherId; }
public void setFinisherId(UUID finisherId) { this.finisherId = finisherId; }
public String getKurzText() { return kurzText; }
public void setKurzText(String kurzText) { this.kurzText = kurzText; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
public GeschlechtEnum getGeschlecht() { return geschlecht; }
public void setGeschlecht(GeschlechtEnum geschlecht) { this.geschlecht = geschlecht; }
public List<Werkzeug> getBenoetigtAktiv() { return benoetigtAktiv; }
public void setBenoetigtAktiv(List<Werkzeug> benoetigtAktiv) { this.benoetigtAktiv = benoetigtAktiv; }
public List<Werkzeug> getBenoetigtPassiv() { return benoetigtPassiv; }
public void setBenoetigtPassiv(List<Werkzeug> benoetigtPassiv) { this.benoetigtPassiv = benoetigtPassiv; }
@Override
public String toString() {
return "Finisher[id=" + finisherId + ", kurzText=" + kurzText + ", geschlecht=" + geschlecht + "]";
}
}

View File

@@ -41,6 +41,12 @@ public class Sperre {
public Integer getMinutenBis() { return minutenBis; }
public void setMinutenBis(Integer minutenBis) { this.minutenBis = minutenBis; }
@Override
public String toString() {
return "Sperre[id=" + sperreId + ", kurzText=" + kurzText
+ ", minuten=" + minutenVon + "-" + minutenBis + ", fuer=" + sperreFuer + ", gruppeId=" + gruppeId + "]";
}
public boolean isAufgabePassend(Mitspieler passiv) {
for (Werkzeug werkzeug : sperreFuer) {
if (!passiv.isVerfuegbar(werkzeug)) {

View File

@@ -8,8 +8,10 @@ import de.oaa.xxx.session.SessionDurchfuehren;
import de.oaa.xxx.session.aufgaben.AufgabenList;
import de.oaa.xxx.session.entity.MitspielerEntity;
import de.oaa.xxx.session.entity.SessionEntity;
import de.oaa.xxx.session.repository.AktiveSperreRepository;
import de.oaa.xxx.session.repository.MitspielerRepository;
import de.oaa.xxx.session.repository.SessionRepository;
import de.oaa.xxx.user.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
@@ -26,6 +28,7 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
@RestController
@@ -37,12 +40,17 @@ public class SessionController {
private final SessionRepository sessionRepository;
private final MitspielerRepository mitspielerRepository;
private final AktiveSperreRepository aktiveSperreRepository;
private final UserRepository userRepository;
private final ObjectMapper objectMapper;
public SessionController(SessionRepository sessionRepository, MitspielerRepository mitspielerRepository,
AktiveSperreRepository aktiveSperreRepository, UserRepository userRepository,
ObjectMapper objectMapper) {
this.sessionRepository = sessionRepository;
this.mitspielerRepository = mitspielerRepository;
this.aktiveSperreRepository = aktiveSperreRepository;
this.userRepository = userRepository;
this.objectMapper = objectMapper;
}
@@ -62,7 +70,9 @@ public class SessionController {
@PostMapping
public ResponseEntity<Void> create(@RequestBody Session session) {
UUID userId = (UUID) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String email = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
UUID userId = userRepository.findByEmail(email).map(u -> u.getUserId()).orElse(null);
if (userId == null) return ResponseEntity.status(401).build();
SessionEntity entity = new SessionEntity();
entity.setSessionId(UUID.randomUUID());
entity.setUserId(userId);
@@ -76,6 +86,10 @@ public class SessionController {
entity.setZeitfaktorZeitstrafen(session.getZeitfaktorZeitstrafen() != null ? session.getZeitfaktorZeitstrafen() : 1.0);
entity.setLevel(1);
sessionRepository.save(entity);
LOGGER.debug("Session gestartet [sessionId={}, userId={}, aufgabenProLevel={}, wahrscheinlichkeitStrafe={}%, wahrscheinlichkeitSperre={}%, zeitfaktorZeitstrafen={}]",
entity.getSessionId(), entity.getUserId(), entity.getAufgabenProLevel(),
entity.getWahrscheinlichkeitStrafe(), entity.getWahrscheinlichkeitSperre(),
entity.getZeitfaktorZeitstrafen());
return ResponseEntity.created(
ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(entity.getSessionId()).toUri()
).build();
@@ -85,6 +99,8 @@ public class SessionController {
public ResponseEntity<Void> deleteSession(@RequestBody Session session) {
return sessionRepository.findById(session.getSessionId())
.map(entity -> {
aktiveSperreRepository.deleteAll(entity.getAktiveSperren());
mitspielerRepository.deleteAll(entity.getMitspieler());
sessionRepository.delete(entity);
return ResponseEntity.accepted().<Void>build();
})
@@ -119,7 +135,22 @@ public class SessionController {
return ResponseEntity.badRequest().build();
}
session.setLetzteAktivitaet(LocalDateTime.now());
AufgabeAnzeige next = new SessionDurchfuehren(session).getNext();
SessionDurchfuehren durchfuehren = new SessionDurchfuehren(session);
AufgabeAnzeige next = durchfuehren.getNext();
session.setLevel(durchfuehren.getLevel());
session.setAufgabenAufAktuellemLevel(durchfuehren.getAufgabenAufAktuellemLevel());
if (next == null) {
return ResponseEntity.noContent().build();
}
next.setLevel(durchfuehren.getLevel());
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Neue Aufgabe [sessionId={}, level={}, aufgaben={}/{}, aktiveSperren={}]",
sessionId, session.getLevel(), session.getAufgabenAufAktuellemLevel(),
session.getAufgabenProLevel(), session.getAktiveSperren().size());
session.getAktiveSperren().forEach(s ->
LOGGER.debug(" Sperre [mitspieler={}, {}min, ende={}]",
s.getMitspieler().getName(), s.getMinuten(), s.getEndzeit()));
}
return ResponseEntity.ok(next);
} catch (Exception exception) {
LOGGER.error(exception.getMessage(), exception);
@@ -150,14 +181,48 @@ public class SessionController {
return ResponseEntity.accepted().build();
}
@GetMapping("/{sessionId}/finisher")
public ResponseEntity<List<AufgabeAnzeige>> getFinisher(@PathVariable UUID sessionId) {
try {
SessionEntity session = sessionRepository.findById(sessionId).orElse(null);
if (session == null) return ResponseEntity.badRequest().build();
SessionDurchfuehren durchfuehren = new SessionDurchfuehren(session);
return ResponseEntity.ok(durchfuehren.getFinisher());
} catch (Exception exception) {
LOGGER.error(exception.getMessage(), exception);
return ResponseEntity.internalServerError().build();
}
}
@PostMapping("/{sessionId}/backToLevel5")
public ResponseEntity<Void> backToLevel5(@PathVariable UUID sessionId) {
try {
SessionEntity session = sessionRepository.findById(sessionId).orElse(null);
if (session == null) return ResponseEntity.badRequest().build();
SessionDurchfuehren durchfuehren = new SessionDurchfuehren(session);
durchfuehren.backToLvl5();
session.setLevel(durchfuehren.getLevel());
session.setAufgabenAufAktuellemLevel(durchfuehren.getAufgabenAufAktuellemLevel());
sessionRepository.save(session);
return ResponseEntity.accepted().build();
} catch (Exception exception) {
LOGGER.error(exception.getMessage(), exception);
return ResponseEntity.internalServerError().build();
}
}
private Session toSession(SessionEntity entity) {
Session session = new Session();
session.setSessionId(entity.getSessionId());
session.setUserId(entity.getUserId());
session.setAufgabenProLevel(entity.getAufgabenAufAktuellemLevel());
session.setAufgabenProLevel(entity.getAufgabenProLevel());
session.setWahrscheinlichkeitSperre(entity.getWahrscheinlichkeitSperre());
session.setWahrscheinlichkeitStrafe(entity.getWahrscheinlichkeitStrafe());
session.setZeitfaktorZeitstrafen(entity.getZeitfaktorZeitstrafen());
session.setLevel(entity.getLevel());
session.setAufgabenAufAktuellemLevel(entity.getAufgabenAufAktuellemLevel());
session.setStartZeit(entity.getStartZeit());
session.setLetzteAktivitaet(entity.getLetzteAktivitaet());
return session;
}
}

View File

@@ -1,6 +1,9 @@
package de.oaa.xxx.session.controller;
import de.oaa.xxx.session.AktiveSperre;
import de.oaa.xxx.session.Mitspieler;
import de.oaa.xxx.session.entity.AktiveSperreEntity;
import de.oaa.xxx.session.entity.SessionEntity;
import de.oaa.xxx.session.repository.AktiveSperreRepository;
import de.oaa.xxx.session.repository.MitspielerRepository;
import de.oaa.xxx.session.repository.SessionRepository;
@@ -21,6 +24,7 @@ import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@RestController("sessionSperreController")
@RequestMapping("/session/sperre")
@@ -70,6 +74,24 @@ public class SperreController {
}
}
@GetMapping("/aktive")
public ResponseEntity<List<AktiveSperre>> getAktiveSperren(@RequestParam UUID sessionId) {
try {
SessionEntity session = sessionRepository.findById(sessionId).orElse(null);
if (session == null) return ResponseEntity.noContent().build();
List<Mitspieler> mitspielerList = session.getMitspieler().stream()
.map(m -> m.toMitspieler())
.collect(Collectors.toList());
List<AktiveSperre> sperren = session.getAktiveSperren().stream()
.map(e -> e.toSperre(mitspielerList))
.collect(Collectors.toList());
return ResponseEntity.ok(sperren);
} catch (Exception exception) {
LOGGER.error(exception.getMessage(), exception);
return ResponseEntity.internalServerError().build();
}
}
@PostMapping("/verlaengern")
public ResponseEntity<Void> aktiveVerlaengern(@RequestBody SperrenVerlaengernCallback callback) {
if (callback == null || callback.getSpielerId() == null || callback.getFaktor() == null) {

View File

@@ -84,6 +84,12 @@ public class AktiveSperreEntity {
return sperre;
}
@Override
public String toString() {
return "AktiveSperreEntity[id=" + aktiveSperreId + ", mitspieler=" + (mitspieler != null ? mitspieler.getName() : null)
+ ", " + minuten + "min, von=" + startzeit + ", bis=" + endzeit + ", fuer=" + fuer + "]";
}
private Mitspieler getMitspielerFromList(List<Mitspieler> mitspielerList, UUID id) {
Optional<Mitspieler> first = mitspielerList.stream().filter(m -> m.getId().equals(id)).findFirst();
return first.orElse(null);

View File

@@ -78,6 +78,12 @@ public class MitspielerEntity {
public List<AktiveSperreEntity> getAktiveSperren() { return aktiveSperren; }
public void setAktiveSperren(List<AktiveSperreEntity> aktiveSperren) { this.aktiveSperren = aktiveSperren; }
@Override
public String toString() {
return "MitspielerEntity[mitspielerId=" + mitspielerId + ", name=" + name
+ ", geschlecht=" + geschlecht + ", rollen=" + rollen + ", werkzeuge=" + werkzeuge + "]";
}
public Mitspieler toMitspieler() {
Mitspieler mitspieler = new Mitspieler();
mitspieler.setGeschlecht(geschlecht);

View File

@@ -82,4 +82,12 @@ public class SessionEntity {
public Double getZeitfaktorZeitstrafen() { return zeitfaktorZeitstrafen; }
public void setZeitfaktorZeitstrafen(Double zeitfaktorZeitstrafen) { this.zeitfaktorZeitstrafen = zeitfaktorZeitstrafen; }
@Override
public String toString() {
return "SessionEntity[sessionId=" + sessionId + ", userId=" + userId
+ ", level=" + level + ", aufgaben=" + aufgabenAufAktuellemLevel + "/" + aufgabenProLevel
+ ", pStrafe=" + wahrscheinlichkeitStrafe + "%, pSperre=" + wahrscheinlichkeitSperre + "%"
+ ", zeitfaktor=" + zeitfaktorZeitstrafen + ", start=" + startZeit + "]";
}
}

View File

@@ -18,4 +18,9 @@ public class SperreCallback extends Callback {
public String getReleaseText() { return releaseText; }
public void setReleaseText(String releaseText) { this.releaseText = releaseText; }
@Override
public String toString() {
return "SperreCallback[sessionId=" + getSessionId() + ", sperreId=" + sperreId + ", spielerId=" + spielerId + "]";
}
}

View File

@@ -14,4 +14,9 @@ public class SperrenVerlaengernCallback extends Callback {
public Integer getFaktor() { return faktor; }
public void setFaktor(Integer faktor) { this.faktor = faktor; }
@Override
public String toString() {
return "SperrenVerlaengernCallback[sessionId=" + getSessionId() + ", spielerId=" + spielerId + ", faktor=" + faktor + "]";
}
}

View File

@@ -0,0 +1,114 @@
package de.oaa.xxx.social;
import de.oaa.xxx.social.dto.ProfileImageDto;
import de.oaa.xxx.social.entity.ProfileImageEntity;
import de.oaa.xxx.social.entity.ProfileImageLikeEntity;
import de.oaa.xxx.social.repository.ProfileImageLikeRepository;
import de.oaa.xxx.social.repository.ProfileImageRepository;
import de.oaa.xxx.user.UserRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/social/profile-images")
public class ProfileImageController {
private static final int MAX_IMAGES_PER_USER = 20;
private final ProfileImageRepository profileImageRepository;
private final ProfileImageLikeRepository profileImageLikeRepository;
private final UserRepository userRepository;
public ProfileImageController(ProfileImageRepository profileImageRepository,
ProfileImageLikeRepository profileImageLikeRepository,
UserRepository userRepository) {
this.profileImageRepository = profileImageRepository;
this.profileImageLikeRepository = profileImageLikeRepository;
this.userRepository = userRepository;
}
record UploadRequest(String imageData) {}
@PostMapping
public ResponseEntity<ProfileImageDto> uploadImage(@RequestBody UploadRequest request, Principal principal) {
var meOpt = userRepository.findByEmail(principal.getName());
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
UUID myId = meOpt.get().getUserId();
if (request.imageData() == null || request.imageData().isBlank()) {
return ResponseEntity.badRequest().build();
}
if (profileImageRepository.countByUserId(myId) >= MAX_IMAGES_PER_USER) {
return ResponseEntity.status(422).build();
}
ProfileImageEntity entity = new ProfileImageEntity();
entity.setImageId(UUID.randomUUID());
entity.setUserId(myId);
entity.setImageData(request.imageData());
entity.setUploadedAt(LocalDateTime.now());
profileImageRepository.save(entity);
return ResponseEntity.status(201).body(toDto(entity, myId));
}
@GetMapping
public ResponseEntity<List<ProfileImageDto>> getImages(@RequestParam UUID userId, Principal principal) {
var meOpt = userRepository.findByEmail(principal.getName());
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
UUID myId = meOpt.get().getUserId();
List<ProfileImageEntity> images = profileImageRepository.findByUserIdOrderByUploadedAtDesc(userId);
List<ProfileImageDto> dtos = images.stream().map(img -> toDto(img, myId)).toList();
return ResponseEntity.ok(dtos);
}
@DeleteMapping("/{imageId}")
public ResponseEntity<Void> deleteImage(@PathVariable UUID imageId, Principal principal) {
var meOpt = userRepository.findByEmail(principal.getName());
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
UUID myId = meOpt.get().getUserId();
var imgOpt = profileImageRepository.findById(imageId);
if (imgOpt.isEmpty()) return ResponseEntity.notFound().build();
if (!imgOpt.get().getUserId().equals(myId)) return ResponseEntity.status(403).build();
profileImageLikeRepository.deleteByImageId(imageId);
profileImageRepository.delete(imgOpt.get());
return ResponseEntity.noContent().build();
}
@PostMapping("/{imageId}/like")
public ResponseEntity<Void> toggleLike(@PathVariable UUID imageId, Principal principal) {
var meOpt = userRepository.findByEmail(principal.getName());
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
UUID myId = meOpt.get().getUserId();
if (profileImageRepository.findById(imageId).isEmpty()) return ResponseEntity.notFound().build();
var existing = profileImageLikeRepository.findByImageIdAndUserId(imageId, myId);
if (existing.isPresent()) {
profileImageLikeRepository.delete(existing.get());
} else {
ProfileImageLikeEntity like = new ProfileImageLikeEntity();
like.setLikeId(UUID.randomUUID());
like.setImageId(imageId);
like.setUserId(myId);
like.setLikedAt(LocalDateTime.now());
profileImageLikeRepository.save(like);
}
return ResponseEntity.ok().build();
}
private ProfileImageDto toDto(ProfileImageEntity entity, UUID myId) {
long likeCount = profileImageLikeRepository.countByImageId(entity.getImageId());
boolean likedByMe = profileImageLikeRepository.findByImageIdAndUserId(entity.getImageId(), myId).isPresent();
return new ProfileImageDto(entity.getImageId(), entity.getUserId(), entity.getImageData(),
entity.getUploadedAt(), likeCount, likedByMe);
}
}

View File

@@ -0,0 +1,273 @@
package de.oaa.xxx.social;
import de.oaa.xxx.social.dto.ConversationSummary;
import de.oaa.xxx.social.dto.FriendshipDto;
import de.oaa.xxx.social.dto.MessageDto;
import de.oaa.xxx.social.dto.UserProfile;
import de.oaa.xxx.social.entity.FriendshipEntity;
import de.oaa.xxx.social.entity.FriendshipEntity.Status;
import de.oaa.xxx.social.entity.MessageEntity;
import de.oaa.xxx.social.repository.FriendshipRepository;
import de.oaa.xxx.social.repository.MessageRepository;
import de.oaa.xxx.user.UserEntity;
import de.oaa.xxx.user.UserRepository;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
import java.time.LocalDateTime;
import java.util.*;
@RestController
@RequestMapping("/social")
public class SocialController {
private final UserRepository userRepository;
private final FriendshipRepository friendshipRepository;
private final MessageRepository messageRepository;
public SocialController(UserRepository userRepository,
FriendshipRepository friendshipRepository,
MessageRepository messageRepository) {
this.userRepository = userRepository;
this.friendshipRepository = friendshipRepository;
this.messageRepository = messageRepository;
}
record FriendRequestBody(UUID receiverId) {}
record FriendshipActionBody(UUID friendshipId) {}
record SendMessageBody(UUID receiverId, String text) {}
// ── User Profile ──
@GetMapping("/users/{userId}")
public ResponseEntity<UserProfile> getUserProfile(@PathVariable UUID userId, Principal principal) {
var meOpt = userRepository.findByEmail(principal.getName());
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
UUID myId = meOpt.get().getUserId();
return userRepository.findById(userId)
.map(u -> ResponseEntity.ok(toUserProfileWithStatus(u, myId)))
.orElse(ResponseEntity.notFound().build());
}
// ── User Search ──
@GetMapping("/users/search")
public ResponseEntity<List<UserProfile>> searchUsers(@RequestParam String q, Principal principal) {
var meOpt = userRepository.findByEmail(principal.getName());
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
UUID myId = meOpt.get().getUserId();
List<UserEntity> results = userRepository.findByNameContainingIgnoreCase(q);
List<UserProfile> profiles = results.stream()
.filter(u -> !u.getUserId().equals(myId))
.limit(20)
.map(u -> toUserProfileWithStatus(u, myId))
.toList();
return ResponseEntity.ok(profiles);
}
// ── Friendship ──
@PostMapping("/friends/request")
public ResponseEntity<Void> sendFriendRequest(@RequestBody FriendRequestBody body, Principal principal) {
var meOpt = userRepository.findByEmail(principal.getName());
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
UUID myId = meOpt.get().getUserId();
if (friendshipRepository.findExisting(myId, body.receiverId()).isPresent()) {
return ResponseEntity.status(409).build();
}
FriendshipEntity f = new FriendshipEntity();
f.setFriendshipId(UUID.randomUUID());
f.setSenderId(myId);
f.setReceiverId(body.receiverId());
f.setStatus(Status.PENDING);
f.setCreatedAt(LocalDateTime.now());
friendshipRepository.save(f);
return ResponseEntity.status(201).build();
}
@PostMapping("/friends/accept")
public ResponseEntity<Void> acceptFriendRequest(@RequestBody FriendshipActionBody body, Principal principal) {
var meOpt = userRepository.findByEmail(principal.getName());
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
UUID myId = meOpt.get().getUserId();
var fOpt = friendshipRepository.findById(body.friendshipId());
if (fOpt.isEmpty()) return ResponseEntity.notFound().build();
FriendshipEntity f = fOpt.get();
if (!f.getReceiverId().equals(myId)) return ResponseEntity.status(403).build();
f.setStatus(Status.ACCEPTED);
friendshipRepository.save(f);
return ResponseEntity.ok().build();
}
@DeleteMapping("/friends/reject")
public ResponseEntity<Void> rejectOrRemoveFriend(@RequestBody FriendshipActionBody body, Principal principal) {
var meOpt = userRepository.findByEmail(principal.getName());
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
UUID myId = meOpt.get().getUserId();
var fOpt = friendshipRepository.findById(body.friendshipId());
if (fOpt.isEmpty()) return ResponseEntity.notFound().build();
FriendshipEntity f = fOpt.get();
if (!f.getSenderId().equals(myId) && !f.getReceiverId().equals(myId)) {
return ResponseEntity.status(403).build();
}
friendshipRepository.delete(f);
return ResponseEntity.noContent().build();
}
@GetMapping("/friends")
public ResponseEntity<List<FriendshipDto>> getFriends(Principal principal) {
var meOpt = userRepository.findByEmail(principal.getName());
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
UUID myId = meOpt.get().getUserId();
List<FriendshipDto> dtos = friendshipRepository.findFriends(myId, Status.ACCEPTED).stream()
.map(f -> {
UUID friendId = f.getSenderId().equals(myId) ? f.getReceiverId() : f.getSenderId();
return userRepository.findById(friendId)
.map(u -> new FriendshipDto(
f.getFriendshipId(),
new UserProfile(u.getUserId(), u.getName(), u.getProfilePicture(), u.getProfilePictureHq(), "FRIEND"),
f.getStatus().name(),
f.getCreatedAt()))
.orElse(null);
})
.filter(Objects::nonNull)
.toList();
return ResponseEntity.ok(dtos);
}
@GetMapping("/friends/pending")
public ResponseEntity<List<FriendshipDto>> getPendingRequests(Principal principal) {
var meOpt = userRepository.findByEmail(principal.getName());
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
UUID myId = meOpt.get().getUserId();
List<FriendshipDto> dtos = friendshipRepository.findByReceiverIdAndStatus(myId, Status.PENDING).stream()
.map(f -> userRepository.findById(f.getSenderId())
.map(u -> new FriendshipDto(
f.getFriendshipId(),
new UserProfile(u.getUserId(), u.getName(), u.getProfilePicture(), u.getProfilePictureHq(), "PENDING_RECEIVED"),
f.getStatus().name(),
f.getCreatedAt()))
.orElse(null))
.filter(Objects::nonNull)
.toList();
return ResponseEntity.ok(dtos);
}
@GetMapping("/friends/pending/count")
public ResponseEntity<Long> getPendingCount(Principal principal) {
var meOpt = userRepository.findByEmail(principal.getName());
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
UUID myId = meOpt.get().getUserId();
return ResponseEntity.ok(friendshipRepository.countByReceiverIdAndStatus(myId, Status.PENDING));
}
// ── Messages ──
@PostMapping("/messages")
public ResponseEntity<Void> sendMessage(@RequestBody SendMessageBody body, Principal principal) {
var meOpt = userRepository.findByEmail(principal.getName());
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
UUID myId = meOpt.get().getUserId();
if (body.text() == null || body.text().isBlank()) return ResponseEntity.badRequest().build();
MessageEntity msg = new MessageEntity();
msg.setMessageId(UUID.randomUUID());
msg.setSenderId(myId);
msg.setReceiverId(body.receiverId());
msg.setText(body.text().trim());
msg.setSentAt(LocalDateTime.now());
messageRepository.save(msg);
return ResponseEntity.status(201).build();
}
@GetMapping("/messages")
public ResponseEntity<List<ConversationSummary>> getConversations(Principal principal) {
var meOpt = userRepository.findByEmail(principal.getName());
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
UUID myId = meOpt.get().getUserId();
List<MessageEntity> allMessages = messageRepository.findAllByUser(myId);
// Group by partner, keep most recent message per partner
Map<UUID, MessageEntity> latestByPartner = new LinkedHashMap<>();
for (MessageEntity m : allMessages) {
UUID partnerId = m.getSenderId().equals(myId) ? m.getReceiverId() : m.getSenderId();
latestByPartner.putIfAbsent(partnerId, m);
}
List<ConversationSummary> summaries = new ArrayList<>();
for (Map.Entry<UUID, MessageEntity> entry : latestByPartner.entrySet()) {
UUID partnerId = entry.getKey();
MessageEntity lastMsg = entry.getValue();
var partnerOpt = userRepository.findById(partnerId);
if (partnerOpt.isEmpty()) continue;
UserProfile partnerProfile = toUserProfileWithStatus(partnerOpt.get(), myId);
MessageDto lastMsgDto = toMessageDto(lastMsg);
long unreadCount = allMessages.stream()
.filter(m -> m.getSenderId().equals(partnerId)
&& m.getReceiverId().equals(myId)
&& m.getReadAt() == null)
.count();
summaries.add(new ConversationSummary(partnerProfile, lastMsgDto, unreadCount));
}
return ResponseEntity.ok(summaries);
}
@GetMapping("/messages/unread/count")
public ResponseEntity<Long> getUnreadCount(Principal principal) {
var meOpt = userRepository.findByEmail(principal.getName());
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
UUID myId = meOpt.get().getUserId();
return ResponseEntity.ok(messageRepository.countUnread(myId));
}
@GetMapping("/messages/{partnerId}")
public ResponseEntity<List<MessageDto>> getConversation(@PathVariable UUID partnerId, Principal principal) {
var meOpt = userRepository.findByEmail(principal.getName());
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
UUID myId = meOpt.get().getUserId();
List<MessageEntity> messages = messageRepository.findConversation(myId, partnerId, PageRequest.of(0, 50));
messageRepository.markAsRead(myId, partnerId, LocalDateTime.now());
return ResponseEntity.ok(messages.stream().map(this::toMessageDto).toList());
}
// ── Helpers ──
private UserProfile toUserProfileWithStatus(UserEntity user, UUID myId) {
String status = "NONE";
var existing = friendshipRepository.findExisting(myId, user.getUserId());
if (existing.isPresent()) {
FriendshipEntity f = existing.get();
if (f.getStatus() == Status.ACCEPTED) {
status = "FRIEND";
} else if (f.getSenderId().equals(myId)) {
status = "PENDING_SENT";
} else {
status = "PENDING_RECEIVED";
}
}
return new UserProfile(user.getUserId(), user.getName(), user.getProfilePicture(), user.getProfilePictureHq(), status);
}
private MessageDto toMessageDto(MessageEntity m) {
String senderName = userRepository.findById(m.getSenderId())
.map(UserEntity::getName)
.orElse("Unbekannt");
return new MessageDto(
m.getMessageId(), m.getSenderId(), senderName,
m.getReceiverId(), m.getText(), m.getSentAt(), m.getReadAt() != null);
}
}

View File

@@ -0,0 +1,3 @@
package de.oaa.xxx.social.dto;
public record ConversationSummary(UserProfile partner, MessageDto lastMessage, long unreadCount) {}

View File

@@ -0,0 +1,6 @@
package de.oaa.xxx.social.dto;
import java.time.LocalDateTime;
import java.util.UUID;
public record FriendshipDto(UUID friendshipId, UserProfile user, String status, LocalDateTime createdAt) {}

View File

@@ -0,0 +1,6 @@
package de.oaa.xxx.social.dto;
import java.time.LocalDateTime;
import java.util.UUID;
public record MessageDto(UUID messageId, UUID senderId, String senderName, UUID receiverId, String text, LocalDateTime sentAt, boolean read) {}

View File

@@ -0,0 +1,7 @@
package de.oaa.xxx.social.dto;
import java.time.LocalDateTime;
import java.util.UUID;
public record ProfileImageDto(UUID imageId, UUID userId, String imageData,
LocalDateTime uploadedAt, long likeCount, boolean likedByMe) {}

View File

@@ -0,0 +1,5 @@
package de.oaa.xxx.social.dto;
import java.util.UUID;
public record UserProfile(UUID userId, String name, String profilePicture, String profilePictureHq, String friendStatus) {}

View File

@@ -0,0 +1,44 @@
package de.oaa.xxx.social.entity;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.UUID;
@Entity
@Table(name = "friendship")
public class FriendshipEntity {
public enum Status { PENDING, ACCEPTED }
@Id
@Column
private UUID friendshipId;
@Column(nullable = false)
private UUID senderId;
@Column(nullable = false)
private UUID receiverId;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 10)
private Status status;
@Column(nullable = false)
private LocalDateTime createdAt;
public UUID getFriendshipId() { return friendshipId; }
public void setFriendshipId(UUID friendshipId) { this.friendshipId = friendshipId; }
public UUID getSenderId() { return senderId; }
public void setSenderId(UUID senderId) { this.senderId = senderId; }
public UUID getReceiverId() { return receiverId; }
public void setReceiverId(UUID receiverId) { this.receiverId = receiverId; }
public Status getStatus() { return status; }
public void setStatus(Status status) { this.status = status; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
}

View File

@@ -0,0 +1,47 @@
package de.oaa.xxx.social.entity;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.UUID;
@Entity
@Table(name = "message")
public class MessageEntity {
@Id
@Column
private UUID messageId;
@Column(nullable = false)
private UUID senderId;
@Column(nullable = false)
private UUID receiverId;
@Column(columnDefinition = "MEDIUMTEXT", nullable = false)
private String text;
@Column(nullable = false)
private LocalDateTime sentAt;
@Column
private LocalDateTime readAt;
public UUID getMessageId() { return messageId; }
public void setMessageId(UUID messageId) { this.messageId = messageId; }
public UUID getSenderId() { return senderId; }
public void setSenderId(UUID senderId) { this.senderId = senderId; }
public UUID getReceiverId() { return receiverId; }
public void setReceiverId(UUID receiverId) { this.receiverId = receiverId; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
public LocalDateTime getSentAt() { return sentAt; }
public void setSentAt(LocalDateTime sentAt) { this.sentAt = sentAt; }
public LocalDateTime getReadAt() { return readAt; }
public void setReadAt(LocalDateTime readAt) { this.readAt = readAt; }
}

View File

@@ -0,0 +1,35 @@
package de.oaa.xxx.social.entity;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.UUID;
@Entity
@Table(name = "profile_image")
public class ProfileImageEntity {
@Id
@Column
private UUID imageId;
@Column(nullable = false)
private UUID userId;
@Column(columnDefinition = "MEDIUMTEXT", nullable = false)
private String imageData;
@Column(nullable = false)
private LocalDateTime uploadedAt;
public UUID getImageId() { return imageId; }
public void setImageId(UUID imageId) { this.imageId = imageId; }
public UUID getUserId() { return userId; }
public void setUserId(UUID userId) { this.userId = userId; }
public String getImageData() { return imageData; }
public void setImageData(String imageData) { this.imageData = imageData; }
public LocalDateTime getUploadedAt() { return uploadedAt; }
public void setUploadedAt(LocalDateTime uploadedAt) { this.uploadedAt = uploadedAt; }
}

View File

@@ -0,0 +1,35 @@
package de.oaa.xxx.social.entity;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.UUID;
@Entity
@Table(name = "profile_image_like")
public class ProfileImageLikeEntity {
@Id
@Column
private UUID likeId;
@Column(nullable = false)
private UUID imageId;
@Column(nullable = false)
private UUID userId;
@Column(nullable = false)
private LocalDateTime likedAt;
public UUID getLikeId() { return likeId; }
public void setLikeId(UUID likeId) { this.likeId = likeId; }
public UUID getImageId() { return imageId; }
public void setImageId(UUID imageId) { this.imageId = imageId; }
public UUID getUserId() { return userId; }
public void setUserId(UUID userId) { this.userId = userId; }
public LocalDateTime getLikedAt() { return likedAt; }
public void setLikedAt(LocalDateTime likedAt) { this.likedAt = likedAt; }
}

View File

@@ -0,0 +1,24 @@
package de.oaa.xxx.social.repository;
import de.oaa.xxx.social.entity.FriendshipEntity;
import de.oaa.xxx.social.entity.FriendshipEntity.Status;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface FriendshipRepository extends JpaRepository<FriendshipEntity, UUID> {
List<FriendshipEntity> findByReceiverIdAndStatus(UUID receiverId, Status status);
long countByReceiverIdAndStatus(UUID receiverId, Status status);
@Query("SELECT f FROM FriendshipEntity f WHERE (f.senderId = :userId OR f.receiverId = :userId) AND f.status = :status")
List<FriendshipEntity> findFriends(@Param("userId") UUID userId, @Param("status") Status status);
@Query("SELECT f FROM FriendshipEntity f WHERE (f.senderId = :userA AND f.receiverId = :userB) OR (f.senderId = :userB AND f.receiverId = :userA)")
Optional<FriendshipEntity> findExisting(@Param("userA") UUID userA, @Param("userB") UUID userB);
}

View File

@@ -0,0 +1,30 @@
package de.oaa.xxx.social.repository;
import de.oaa.xxx.social.entity.MessageEntity;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
public interface MessageRepository extends JpaRepository<MessageEntity, UUID> {
@Query("SELECT m FROM MessageEntity m WHERE (m.senderId = :userA AND m.receiverId = :userB) OR (m.senderId = :userB AND m.receiverId = :userA) ORDER BY m.sentAt DESC")
List<MessageEntity> findConversation(@Param("userA") UUID userA, @Param("userB") UUID userB, Pageable pageable);
@Query("SELECT m FROM MessageEntity m WHERE m.senderId = :userId OR m.receiverId = :userId ORDER BY m.sentAt DESC")
List<MessageEntity> findAllByUser(@Param("userId") UUID userId);
@Query("SELECT COUNT(m) FROM MessageEntity m WHERE m.receiverId = :userId AND m.readAt IS NULL")
long countUnread(@Param("userId") UUID userId);
@Modifying
@Transactional
@Query("UPDATE MessageEntity m SET m.readAt = :now WHERE m.senderId = :partnerId AND m.receiverId = :userId AND m.readAt IS NULL")
void markAsRead(@Param("userId") UUID userId, @Param("partnerId") UUID partnerId, @Param("now") LocalDateTime now);
}

View File

@@ -0,0 +1,28 @@
package de.oaa.xxx.social.repository;
import de.oaa.xxx.social.entity.ProfileImageLikeEntity;
import jakarta.transaction.Transactional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Optional;
import java.util.UUID;
public interface ProfileImageLikeRepository extends JpaRepository<ProfileImageLikeEntity, UUID> {
Optional<ProfileImageLikeEntity> findByImageIdAndUserId(UUID imageId, UUID userId);
long countByImageId(UUID imageId);
@Modifying
@Transactional
@Query("DELETE FROM ProfileImageLikeEntity l WHERE l.imageId = :imageId")
void deleteByImageId(@Param("imageId") UUID imageId);
@Modifying
@Transactional
@Query("DELETE FROM ProfileImageLikeEntity l WHERE l.userId = :userId")
void deleteByUserId(@Param("userId") UUID userId);
}

View File

@@ -0,0 +1,14 @@
package de.oaa.xxx.social.repository;
import de.oaa.xxx.social.entity.ProfileImageEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.UUID;
public interface ProfileImageRepository extends JpaRepository<ProfileImageEntity, UUID> {
List<ProfileImageEntity> findByUserIdOrderByUploadedAtDesc(UUID userId);
long countByUserId(UUID userId);
}

Some files were not shown because too many files have changed in this diff Show More