Wetier am Cahstity game gebasterln
This commit is contained in:
@@ -13,7 +13,15 @@
|
|||||||
"Read(//tmp/**)",
|
"Read(//tmp/**)",
|
||||||
"Bash(cd:*)",
|
"Bash(cd:*)",
|
||||||
"Bash(JAVA_HOME=\"/snap/eclipse/132/usr/lib/eclipse/plugins/org.eclipse.justj.openjdk.hotspot.jre.full.linux.x86_64_21.0.10.v20260205-0638/jre\"; JAVA_HOME=\"$JAVA_HOME\" PATH=\"$JAVA_HOME/bin:$PATH\" ./gradlew compileJava 2>&1 | grep -E \"error:|ERROR|warning:|WARN|BUILD\" | head -30)",
|
"Bash(JAVA_HOME=\"/snap/eclipse/132/usr/lib/eclipse/plugins/org.eclipse.justj.openjdk.hotspot.jre.full.linux.x86_64_21.0.10.v20260205-0638/jre\"; JAVA_HOME=\"$JAVA_HOME\" PATH=\"$JAVA_HOME/bin:$PATH\" ./gradlew compileJava 2>&1 | grep -E \"error:|ERROR|warning:|WARN|BUILD\" | head -30)",
|
||||||
"Bash(JAVA_HOME=\"/snap/eclipse/132/usr/lib/eclipse/plugins/org.eclipse.justj.openjdk.hotspot.jre.full.linux.x86_64_21.0.10.v20260205-0638/jre\" PATH=\"$JAVA_HOME/bin:$PATH\" ./gradlew compileJava 2>&1 | grep -E \"error:|BUILD\" | head -30)"
|
"Bash(JAVA_HOME=\"/snap/eclipse/132/usr/lib/eclipse/plugins/org.eclipse.justj.openjdk.hotspot.jre.full.linux.x86_64_21.0.10.v20260205-0638/jre\" PATH=\"$JAVA_HOME/bin:$PATH\" ./gradlew compileJava 2>&1 | grep -E \"error:|BUILD\" | head -30)",
|
||||||
|
"Bash(./gradlew compileJava -q 2>&1 | tail -5)",
|
||||||
|
"Bash(./gradlew compileJava -q 2>&1 | tail -20)",
|
||||||
|
"Bash(./gradlew compileJava -q 2>&1 | tail -10)",
|
||||||
|
"Bash(./gradlew compileJava 2>&1 | tail -10)",
|
||||||
|
"Bash(./gradlew compileJava 2>&1 | tail -20)",
|
||||||
|
"Bash(./gradlew compileJava 2>&1 | tail -15)",
|
||||||
|
"Bash(./gradlew compileJava -q 2>&1 | tail -30)",
|
||||||
|
"Bash(ls -lah /home/mario/Workspaces/xxx-thegame/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/*.java)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#Mon Mar 16 20:07:41 CET 2026
|
#Tue Mar 17 19:49:49 CET 2026
|
||||||
display=\:0
|
display=\:0
|
||||||
host=Mario-Linux
|
host=Mario-Linux
|
||||||
process-id=73435
|
process-id=140659
|
||||||
user=mario
|
user=mario
|
||||||
|
|||||||
2755
.metadata/.log
2755
.metadata/.log
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
[ {
|
[ {
|
||||||
"version" : "9.4.1-20260316020012+0000",
|
"version" : "9.4.1-20260317014409+0000",
|
||||||
"buildTime" : "20260316020012+0000",
|
"buildTime" : "20260317014409+0000",
|
||||||
"commitId" : "bf53443ce3925ea6141adbe2d2fd96493865b6ee",
|
"commitId" : "0a84d6751921709fbc7d552a50d9e3b34eb1930d",
|
||||||
"current" : false,
|
"current" : false,
|
||||||
"snapshot" : true,
|
"snapshot" : true,
|
||||||
"nightly" : false,
|
"nightly" : false,
|
||||||
@@ -10,15 +10,15 @@
|
|||||||
"rcFor" : "",
|
"rcFor" : "",
|
||||||
"milestoneFor" : "",
|
"milestoneFor" : "",
|
||||||
"broken" : false,
|
"broken" : false,
|
||||||
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.4.1-20260316020012+0000-bin.zip",
|
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.4.1-20260317014409+0000-bin.zip",
|
||||||
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.4.1-20260316020012+0000-bin.zip.sha256",
|
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.4.1-20260317014409+0000-bin.zip.sha256",
|
||||||
"checksum" : "2e805c642bbf8b8ccf39cf855b202bed260018f7ca61c37408c7f4c32a36a3e2",
|
"checksum" : "b0756f51c7876351c3c18ee7c0777bd2c6b2e18bf4bf31dc17d367310f6cce5c",
|
||||||
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.4.1-20260316020012+0000-wrapper.jar.sha256",
|
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.4.1-20260317014409+0000-wrapper.jar.sha256",
|
||||||
"wrapperChecksum" : "55243ef57851f12b070ad14f7f5bb8302daceeebc5bce5ece5fa6edb23e1145c"
|
"wrapperChecksum" : "55243ef57851f12b070ad14f7f5bb8302daceeebc5bce5ece5fa6edb23e1145c"
|
||||||
}, {
|
}, {
|
||||||
"version" : "9.5.0-20260316005115+0000",
|
"version" : "9.5.0-20260317004629+0000",
|
||||||
"buildTime" : "20260316005115+0000",
|
"buildTime" : "20260317004629+0000",
|
||||||
"commitId" : "b2fdb4cec79e4c0b1b5c1bfeebc8d4b5fd65c690",
|
"commitId" : "644201d0b1d42cde4ea13645efefbdbf004cd6b1",
|
||||||
"current" : false,
|
"current" : false,
|
||||||
"snapshot" : true,
|
"snapshot" : true,
|
||||||
"nightly" : true,
|
"nightly" : true,
|
||||||
@@ -27,10 +27,10 @@
|
|||||||
"rcFor" : "",
|
"rcFor" : "",
|
||||||
"milestoneFor" : "",
|
"milestoneFor" : "",
|
||||||
"broken" : false,
|
"broken" : false,
|
||||||
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260316005115+0000-bin.zip",
|
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260317004629+0000-bin.zip",
|
||||||
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260316005115+0000-bin.zip.sha256",
|
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260317004629+0000-bin.zip.sha256",
|
||||||
"checksum" : "bde9dc37434e5942fdd1e2f9c6f9571fc649224053e691021e2e87ba5af4a2b7",
|
"checksum" : "fa62a8b9f5ed9a718f7a8d9e0d3221ea5a8b15dd1f07c8edb72e871615c1eac0",
|
||||||
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260316005115+0000-wrapper.jar.sha256",
|
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260317004629+0000-wrapper.jar.sha256",
|
||||||
"wrapperChecksum" : "7ef3d73bd95c047814d76ec8324f72deefb96593eb9ce87aa06ecdcdaba7ffe8"
|
"wrapperChecksum" : "7ef3d73bd95c047814d76ec8324f72deefb96593eb9ce87aa06ecdcdaba7ffe8"
|
||||||
}, {
|
}, {
|
||||||
"version" : "9.5.0-milestone-7",
|
"version" : "9.5.0-milestone-7",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -3,7 +3,7 @@
|
|||||||
<section name="org.eclipse.epp.internal.mpc.ui.wizards.MarketplaceWizardDialog_dialogBounds.absolute">
|
<section name="org.eclipse.epp.internal.mpc.ui.wizards.MarketplaceWizardDialog_dialogBounds.absolute">
|
||||||
<item key="DIALOG_X_ORIGIN" value="26"/>
|
<item key="DIALOG_X_ORIGIN" value="26"/>
|
||||||
<item key="DIALOG_Y_ORIGIN" value="23"/>
|
<item key="DIALOG_Y_ORIGIN" value="23"/>
|
||||||
<item key="DIALOG_WIDTH" value="613"/>
|
<item key="DIALOG_WIDTH" value="791"/>
|
||||||
<item key="DIALOG_HEIGHT" value="890"/>
|
<item key="DIALOG_HEIGHT" value="890"/>
|
||||||
<item key="DIALOG_FONT_NAME" value="1|Ubuntu Sans|11.0|0|GTK|1|"/>
|
<item key="DIALOG_FONT_NAME" value="1|Ubuntu Sans|11.0|0|GTK|1|"/>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,5 @@
|
|||||||
INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core
|
INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core
|
||||||
690321491.index
|
1256436118.index
|
||||||
1872440599.index
|
1872440599.index
|
||||||
2240786275.index
|
2240786275.index
|
||||||
4150628576.index
|
4150628576.index
|
||||||
@@ -8,13 +8,14 @@ INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.ec
|
|||||||
2982788279.index
|
2982788279.index
|
||||||
2626965509.index
|
2626965509.index
|
||||||
2609856074.index
|
2609856074.index
|
||||||
2769879155.index
|
|
||||||
4134502745.index
|
4134502745.index
|
||||||
2817101718.index
|
2817101718.index
|
||||||
4158338144.index
|
4158338144.index
|
||||||
519552992.index
|
519552992.index
|
||||||
2503368578.index
|
2503368578.index
|
||||||
2181028596.index
|
2181028596.index
|
||||||
|
9341915.index
|
||||||
|
900586112.index
|
||||||
1453089870.index
|
1453089870.index
|
||||||
2593736024.index
|
2593736024.index
|
||||||
815902026.index
|
815902026.index
|
||||||
@@ -23,6 +24,7 @@ INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.ec
|
|||||||
96642630.index
|
96642630.index
|
||||||
2488355463.index
|
2488355463.index
|
||||||
1446719945.index
|
1446719945.index
|
||||||
|
2721734530.index
|
||||||
1118739196.index
|
1118739196.index
|
||||||
2891161224.index
|
2891161224.index
|
||||||
2047888269.index
|
2047888269.index
|
||||||
@@ -43,11 +45,11 @@ INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.ec
|
|||||||
1732769785.index
|
1732769785.index
|
||||||
2838468603.index
|
2838468603.index
|
||||||
1436262503.index
|
1436262503.index
|
||||||
3662169204.index
|
2668411497.index
|
||||||
2927822381.index
|
2927822381.index
|
||||||
2398089967.index
|
2398089967.index
|
||||||
225562445.index
|
225562445.index
|
||||||
2668411497.index
|
3662169204.index
|
||||||
1295630681.index
|
1295630681.index
|
||||||
3135354350.index
|
3135354350.index
|
||||||
3602551868.index
|
3602551868.index
|
||||||
@@ -100,6 +102,6 @@ INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.ec
|
|||||||
198314732.index
|
198314732.index
|
||||||
3514612140.index
|
3514612140.index
|
||||||
1324521365.index
|
1324521365.index
|
||||||
2318770678.index
|
|
||||||
1633924572.index
|
1633924572.index
|
||||||
1256436118.index
|
2318770678.index
|
||||||
|
690321491.index
|
||||||
|
|||||||
Binary file not shown.
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<dirs>
|
<dirs>
|
||||||
<entry loc="/snap/eclipse/131/usr/lib/eclipse/plugins/org.eclipse.justj.openjdk.hotspot.jre.full.linux.x86_64_21.0.9.v20251105-0741/jre" stamp="1765305526000"/>
|
<entry loc="/usr/lib/jvm/java-21-openjdk-amd64" stamp="1773694107630"/>
|
||||||
</dirs>
|
</dirs>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
<entry path="null"/>
|
<entry path="null"/>
|
||||||
</endorsedDirs>
|
</endorsedDirs>
|
||||||
</libraryInfo>
|
</libraryInfo>
|
||||||
|
<libraryInfo home="/usr/lib/jvm/java-21-openjdk-amd64" version="21.0.10"/>
|
||||||
<libraryInfo home="/snap/eclipse/131/usr/lib/eclipse/plugins/org.eclipse.justj.openjdk.hotspot.jre.full.linux.x86_64_21.0.9.v20251105-0741" version="21.0.9">
|
<libraryInfo home="/snap/eclipse/131/usr/lib/eclipse/plugins/org.eclipse.justj.openjdk.hotspot.jre.full.linux.x86_64_21.0.9.v20251105-0741" version="21.0.9">
|
||||||
<bootpath>
|
<bootpath>
|
||||||
<entry path="null"/>
|
<entry path="null"/>
|
||||||
@@ -23,5 +24,4 @@
|
|||||||
<entry path="null"/>
|
<entry path="null"/>
|
||||||
</endorsedDirs>
|
</endorsedDirs>
|
||||||
</libraryInfo>
|
</libraryInfo>
|
||||||
<libraryInfo home="/snap/eclipse/131/usr/lib/eclipse/plugins/org.eclipse.justj.openjdk.hotspot.jre.full.linux.x86_64_21.0.9.v20251105-0741/jre" version="21.0.9"/>
|
|
||||||
</libraryInfos>
|
</libraryInfos>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<typeInfoHistroy>
|
<typeInfoHistroy>
|
||||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.aufgaben.controller{AboController.java[AboController" modifiers="1" timestamp="1773400404000"/>
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.aufgaben.controller{AboController.java[AboController" modifiers="1" timestamp="1773400404000"/>
|
||||||
|
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.cardlock{CardLockService.java[CardLockService" modifiers="1" timestamp="1773773086076"/>
|
||||||
</typeInfoHistroy>
|
</typeInfoHistroy>
|
||||||
|
|||||||
@@ -7,11 +7,15 @@
|
|||||||
<fullyQualifiedTypeName name="org.hibernate.grammars.hql.HqlParser.LocalDateTimeContext"/>
|
<fullyQualifiedTypeName name="org.hibernate.grammars.hql.HqlParser.LocalDateTimeContext"/>
|
||||||
<fullyQualifiedTypeName name="java.util.stream.Collectors"/>
|
<fullyQualifiedTypeName name="java.util.stream.Collectors"/>
|
||||||
<fullyQualifiedTypeName name="java.time.LocalDate"/>
|
<fullyQualifiedTypeName name="java.time.LocalDate"/>
|
||||||
<fullyQualifiedTypeName name="java.time.LocalDateTime"/>
|
|
||||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.cardlock.CardDTO"/>
|
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.cardlock.CardDTO"/>
|
||||||
<fullyQualifiedTypeName name="java.lang.Enum"/>
|
<fullyQualifiedTypeName name="java.lang.Enum"/>
|
||||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.cardlock.GreenCard"/>
|
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.cardlock.GreenCard"/>
|
||||||
<fullyQualifiedTypeName name="java.util.Random"/>
|
<fullyQualifiedTypeName name="java.util.Random"/>
|
||||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.CardLockService"/>
|
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.CardLockService"/>
|
||||||
<fullyQualifiedTypeName name="jakarta.persistence.Column"/>
|
<fullyQualifiedTypeName name="jakarta.persistence.Column"/>
|
||||||
|
<fullyQualifiedTypeName name="java.time.LocalDateTime"/>
|
||||||
|
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.history.LockHistoryRepository"/>
|
||||||
|
<fullyQualifiedTypeName name="lombok.Getter"/>
|
||||||
|
<fullyQualifiedTypeName name="lombok.Setter"/>
|
||||||
|
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.cardlock.Test"/>
|
||||||
</qualifiedTypeNameHistroy>
|
</qualifiedTypeNameHistroy>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<item key="layout" value="1"/>
|
<item key="layout" value="1"/>
|
||||||
<item key="rootMode" value="1"/>
|
<item key="rootMode" value="1"/>
|
||||||
<item key="linkWithEditor" value="true"/>
|
<item key="linkWithEditor" value="true"/>
|
||||||
<item key="memento" value="<?xml version="1.0" encoding="UTF-8"?>
<packageExplorer group_libraries="1" layout="1" linkWithEditor="1" rootMode="1" workingSetName="Aggregate for window 1773600542055">
<customFilters userDefinedPatternsEnabled="false">
<xmlDefinedFilters>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.StaticsFilter" isEnabled="false"/>
<child filterId="org.eclipse.buildship.ui.packageexplorer.filter.gradle.buildfolder" isEnabled="true"/>
<child filterId="org.eclipse.mylyn.java.ui.MembersFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.NonJavaProjectsFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer_patternFilterId_.*" isEnabled="true"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.NonSharedProjectsFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.SyntheticMembersFilter" isEnabled="true"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.ContainedLibraryFilter" isEnabled="false"/>
<child filterId="org.eclipse.m2e.MavenModuleFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.internal.ui.PackageExplorer.HideInnerClassFilesFilter" isEnabled="true"/>
<child filterId="org.eclipse.jdt.internal.ui.PackageExplorer.EmptyInnerPackageFilter" isEnabled="true"/>
<child filterId="org.eclipse.buildship.ui.packageexplorer.filter.gradle.subProject" isEnabled="true"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.ClosedProjectsFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.DeprecatedMembersFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.EmptyLibraryContainerFilter" isEnabled="true"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.PackageDeclarationFilter" isEnabled="true"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.ImportDeclarationFilter" isEnabled="true"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.NonJavaElementFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.LibraryFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.CuAndClassFileFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.internal.ui.PackageExplorer.EmptyPackageFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.NonPublicFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.LocalTypesFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.FieldsFilter" isEnabled="false"/>
</xmlDefinedFilters>
</customFilters>
</packageExplorer>"/>
|
<item key="memento" value="<?xml version="1.0" encoding="UTF-8"?>
<packageExplorer group_libraries="1" layout="1" linkWithEditor="1" rootMode="1" workingSetName="Aggregate for window 1773600542055">
<customFilters userDefinedPatternsEnabled="false">
<xmlDefinedFilters>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.StaticsFilter" isEnabled="false"/>
<child filterId="org.eclipse.buildship.ui.packageexplorer.filter.gradle.buildfolder" isEnabled="true"/>
<child filterId="org.eclipse.mylyn.java.ui.MembersFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.NonJavaProjectsFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer_patternFilterId_.*" isEnabled="true"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.NonSharedProjectsFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.SyntheticMembersFilter" isEnabled="true"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.ContainedLibraryFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.internal.ui.PackageExplorer.HideInnerClassFilesFilter" isEnabled="true"/>
<child filterId="org.eclipse.jdt.internal.ui.PackageExplorer.EmptyInnerPackageFilter" isEnabled="true"/>
<child filterId="org.eclipse.m2e.MavenModuleFilter" isEnabled="false"/>
<child filterId="org.eclipse.buildship.ui.packageexplorer.filter.gradle.subProject" isEnabled="true"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.ClosedProjectsFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.DeprecatedMembersFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.EmptyLibraryContainerFilter" isEnabled="true"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.PackageDeclarationFilter" isEnabled="true"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.ImportDeclarationFilter" isEnabled="true"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.NonJavaElementFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.LibraryFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.CuAndClassFileFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.internal.ui.PackageExplorer.EmptyPackageFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.NonPublicFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.LocalTypesFilter" isEnabled="false"/>
<child filterId="org.eclipse.jdt.ui.PackageExplorer.FieldsFilter" isEnabled="false"/>
</xmlDefinedFilters>
</customFilters>
</packageExplorer>"/>
|
||||||
</section>
|
</section>
|
||||||
<section name="JavaElementSearchActions">
|
<section name="JavaElementSearchActions">
|
||||||
</section>
|
</section>
|
||||||
@@ -58,4 +58,14 @@
|
|||||||
<item key="org.eclipse.jdt.internal.ui.text.JavaOutlineInformationControlDIALOG_USE_PERSISTED_SIZE" value="true"/>
|
<item key="org.eclipse.jdt.internal.ui.text.JavaOutlineInformationControlDIALOG_USE_PERSISTED_SIZE" value="true"/>
|
||||||
<item key="org.eclipse.jdt.internal.ui.text.JavaOutlineInformationControlDIALOG_USE_PERSISTED_LOCATION" value="false"/>
|
<item key="org.eclipse.jdt.internal.ui.text.JavaOutlineInformationControlDIALOG_USE_PERSISTED_LOCATION" value="false"/>
|
||||||
</section>
|
</section>
|
||||||
|
<section name="NewPackageCreationWizard.dialogBounds">
|
||||||
|
<item key="DIALOG_X_ORIGIN" value="20"/>
|
||||||
|
<item key="DIALOG_Y_ORIGIN" value="20"/>
|
||||||
|
<item key="DIALOG_WIDTH" value="613"/>
|
||||||
|
<item key="DIALOG_HEIGHT" value="500"/>
|
||||||
|
<item key="DIALOG_FONT_NAME" value="1|Ubuntu Sans|11.0|0|GTK|1|"/>
|
||||||
|
</section>
|
||||||
|
<section name="BuildPathsPropertyPage">
|
||||||
|
<item key="pageIndex" value="3"/>
|
||||||
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -17,3 +17,6 @@
|
|||||||
2026-03-16 08:15:37,891 [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-16 08:15:37,891 [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-16 19:25:54,797 [Worker-7: 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-16 19:25:54,797 [Worker-7: 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-16 20:07:45,547 [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-16 20:07:45,547 [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-17 07:10:24,915 [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-17 19:39:55,931 [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-17 19:49:51,508 [Worker-2: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#Mon Mar 16 20:07:41 CET 2026
|
#Tue Mar 17 19:49:49 CET 2026
|
||||||
org.eclipse.core.runtime=2
|
org.eclipse.core.runtime=2
|
||||||
org.eclipse.platform=4.39.0.v20260226-0420
|
org.eclipse.platform=4.39.0.v20260226-0420
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
compileOnly("org.projectlombok:lombok")
|
||||||
|
annotationProcessor("org.projectlombok:lombok")
|
||||||
|
testCompileOnly("org.projectlombok:lombok")
|
||||||
|
testAnnotationProcessor("org.projectlombok:lombok")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package de.oaa.xxx;
|
|||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
|
@EnableScheduling
|
||||||
public class XxxThegameApplication {
|
public class XxxThegameApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package de.oaa.xxx.config;
|
package de.oaa.xxx.config;
|
||||||
|
|
||||||
|
import jakarta.servlet.DispatcherType;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
@@ -30,6 +31,7 @@ public class SecurityConfig {
|
|||||||
.authenticationEntryPoint((request, response, authException) ->
|
.authenticationEntryPoint((request, response, authException) ->
|
||||||
response.sendRedirect("/login.html")))
|
response.sendRedirect("/login.html")))
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
.dispatcherTypeMatchers(DispatcherType.ASYNC, DispatcherType.ERROR).permitAll()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/")).permitAll()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/")).permitAll()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/error")).permitAll()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/error")).permitAll()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/userhome.html")).authenticated()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/userhome.html")).authenticated()
|
||||||
@@ -43,7 +45,8 @@ public class SecurityConfig {
|
|||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionvanilla.html")).authenticated()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionvanilla.html")).authenticated()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionbdsm.html")).authenticated()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionbdsm.html")).authenticated()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionchastity.html")).authenticated()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionchastity.html")).authenticated()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionchastityingame.html")).authenticated()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/neulock.html")).authenticated()
|
||||||
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/activelock.html")).authenticated()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionbdsmtasks.html")).authenticated()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionbdsmtasks.html")).authenticated()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionbdsmtoys.html")).authenticated()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionbdsmtoys.html")).authenticated()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionbdsmingame.html")).authenticated()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/sessionbdsmingame.html")).authenticated()
|
||||||
@@ -57,15 +60,20 @@ public class SecurityConfig {
|
|||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/communityvotes.html")).authenticated()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/communityvotes.html")).authenticated()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/keyholder.html")).authenticated()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/keyholder.html")).authenticated()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/meine-locks.html")).authenticated()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/meine-locks.html")).authenticated()
|
||||||
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/unlock-history.html")).authenticated()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/einladungen.html")).authenticated()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/einladungen.html")).authenticated()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/joinlock.html")).authenticated()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/joinlock.html")).authenticated()
|
||||||
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/benachrichtigungen.html")).authenticated()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/gruppen/**")).authenticated()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/gruppen/**")).authenticated()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/feed/**")).authenticated()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/feed/**")).authenticated()
|
||||||
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/notifications/**")).authenticated()
|
||||||
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/events/**")).authenticated()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/*.html")).permitAll()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/*.html")).permitAll()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/css/**")).permitAll()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/css/**")).permitAll()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/js/**")).permitAll()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/js/**")).permitAll()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/images/**")).permitAll()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/images/**")).permitAll()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/favicon.ico")).permitAll()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/favicon.ico")).permitAll()
|
||||||
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/audio/**")).permitAll()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/*.png")).permitAll()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/*.png")).permitAll()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/*.jpg")).permitAll()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/*.jpg")).permitAll()
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/*.svg")).permitAll()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/*.svg")).permitAll()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import de.oaa.xxx.games.chastity.cardlock.CardlockRepository;
|
import de.oaa.xxx.games.chastity.cardlock.CardlockRepository;
|
||||||
|
import de.oaa.xxx.social.SseService;
|
||||||
import de.oaa.xxx.social.entity.MessageEntity;
|
import de.oaa.xxx.social.entity.MessageEntity;
|
||||||
import de.oaa.xxx.social.repository.MessageRepository;
|
import de.oaa.xxx.social.repository.MessageRepository;
|
||||||
import de.oaa.xxx.user.UserRepository;
|
import de.oaa.xxx.user.UserRepository;
|
||||||
@@ -34,6 +35,7 @@ public class LockeeInvitationController {
|
|||||||
private final CardlockRepository cardlockRepository;
|
private final CardlockRepository cardlockRepository;
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final MessageRepository messageRepository;
|
private final MessageRepository messageRepository;
|
||||||
|
private final SseService sseService;
|
||||||
|
|
||||||
@Value("${app.base-url:http://localhost:8080}")
|
@Value("${app.base-url:http://localhost:8080}")
|
||||||
private String baseUrl;
|
private String baseUrl;
|
||||||
@@ -43,11 +45,27 @@ public class LockeeInvitationController {
|
|||||||
public LockeeInvitationController(LockeeInvitationRepository lockeeInvitationRepository,
|
public LockeeInvitationController(LockeeInvitationRepository lockeeInvitationRepository,
|
||||||
CardlockRepository cardlockRepository,
|
CardlockRepository cardlockRepository,
|
||||||
UserRepository userRepository,
|
UserRepository userRepository,
|
||||||
MessageRepository messageRepository) {
|
MessageRepository messageRepository,
|
||||||
|
SseService sseService) {
|
||||||
this.lockeeInvitationRepository = lockeeInvitationRepository;
|
this.lockeeInvitationRepository = lockeeInvitationRepository;
|
||||||
this.cardlockRepository = cardlockRepository;
|
this.cardlockRepository = cardlockRepository;
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.messageRepository = messageRepository;
|
this.messageRepository = messageRepository;
|
||||||
|
this.sseService = sseService;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessage(UUID senderId, UUID receiverId, String text, String targetUrl) {
|
||||||
|
MessageEntity msg = new MessageEntity();
|
||||||
|
msg.setMessageId(UUID.randomUUID());
|
||||||
|
msg.setSenderId(senderId);
|
||||||
|
msg.setReceiverId(receiverId);
|
||||||
|
msg.setText(text);
|
||||||
|
msg.setSentAt(LocalDateTime.now());
|
||||||
|
msg.setSystemMessage(true);
|
||||||
|
if (targetUrl != null) msg.setTargetUrl(targetUrl);
|
||||||
|
messageRepository.save(msg);
|
||||||
|
long unread = messageRepository.countByReceiverIdAndSystemMessageAndReadAtIsNull(receiverId, true);
|
||||||
|
sseService.push(receiverId, "NOTIFICATION", java.util.Map.of("unreadCount", unread, "text", text));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String generateUnlockCode(int lines) {
|
private String generateUnlockCode(int lines) {
|
||||||
@@ -134,13 +152,9 @@ public class LockeeInvitationController {
|
|||||||
var lock = lockOpt.get();
|
var lock = lockOpt.get();
|
||||||
cardlockRepository.delete(lock);
|
cardlockRepository.delete(lock);
|
||||||
String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock";
|
String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock";
|
||||||
MessageEntity msg = new MessageEntity();
|
sendMessage(myId, inv.getLockeeUserId(),
|
||||||
msg.setMessageId(UUID.randomUUID());
|
me.getName() + " hat die Lockee-Einladung für das Lock „" + lockName + "\" zurückgezogen.",
|
||||||
msg.setSenderId(myId);
|
null);
|
||||||
msg.setReceiverId(inv.getLockeeUserId());
|
|
||||||
msg.setText(me.getName() + " hat die Lockee-Einladung für das Lock „" + lockName + "\" zurückgezogen.");
|
|
||||||
msg.setSentAt(LocalDateTime.now());
|
|
||||||
messageRepository.save(msg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
@@ -232,14 +246,9 @@ public class LockeeInvitationController {
|
|||||||
lockeeInvitationRepository.delete(inv);
|
lockeeInvitationRepository.delete(inv);
|
||||||
|
|
||||||
String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock";
|
String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock";
|
||||||
MessageEntity msg = new MessageEntity();
|
sendMessage(myId, inv.getKeyholderUserId(),
|
||||||
msg.setMessageId(UUID.randomUUID());
|
me.getName() + " hat die Einladung als Lockee für das Lock „" + lockName + "\" angenommen.",
|
||||||
msg.setSenderId(myId);
|
"/keyholder.html");
|
||||||
msg.setReceiverId(inv.getKeyholderUserId());
|
|
||||||
msg.setText(me.getName() + " hat die Einladung als Lockee für das Lock „" + lockName + "\" angenommen.\n\n" +
|
|
||||||
"Deine Keyholder-Seite: " + baseUrl + "/keyholder.html");
|
|
||||||
msg.setSentAt(now);
|
|
||||||
messageRepository.save(msg);
|
|
||||||
|
|
||||||
return ResponseEntity.ok(Map.of(
|
return ResponseEntity.ok(Map.of(
|
||||||
"lockId", lock.getLockId().toString(),
|
"lockId", lock.getLockId().toString(),
|
||||||
@@ -267,13 +276,9 @@ public class LockeeInvitationController {
|
|||||||
var lock = lockOpt.get();
|
var lock = lockOpt.get();
|
||||||
cardlockRepository.delete(lock);
|
cardlockRepository.delete(lock);
|
||||||
String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock";
|
String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock";
|
||||||
MessageEntity msg = new MessageEntity();
|
sendMessage(myId, inv.getKeyholderUserId(),
|
||||||
msg.setMessageId(UUID.randomUUID());
|
me.getName() + " hat die Einladung als Lockee für das Lock „" + lockName + "\" abgelehnt.",
|
||||||
msg.setSenderId(myId);
|
null);
|
||||||
msg.setReceiverId(inv.getKeyholderUserId());
|
|
||||||
msg.setText(me.getName() + " hat die Einladung als Lockee für das Lock „" + lockName + "\" abgelehnt.");
|
|
||||||
msg.setSentAt(LocalDateTime.now());
|
|
||||||
messageRepository.save(msg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package de.oaa.xxx.games.chastity.cardlock;
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
import de.oaa.xxx.games.chastity.CardLockService;
|
|
||||||
|
|
||||||
public interface Card {
|
public interface Card {
|
||||||
|
|
||||||
public CardDTO processCard(CardLockService lock);
|
public CardDTO processCard(CardLockService lock);
|
||||||
|
|||||||
@@ -1,27 +1,5 @@
|
|||||||
package de.oaa.xxx.games.chastity.cardlock;
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
import de.oaa.xxx.games.chastity.CardLockService;
|
|
||||||
import de.oaa.xxx.games.chastity.KeyholderInvitationEntity;
|
|
||||||
import de.oaa.xxx.games.chastity.KeyholderInvitationRepository;
|
|
||||||
import de.oaa.xxx.games.chastity.LockeeInvitationEntity;
|
|
||||||
import de.oaa.xxx.games.chastity.LockeeInvitationRepository;
|
|
||||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
|
||||||
import de.oaa.xxx.games.chastity.verification.VerificationRepository;
|
|
||||||
import de.oaa.xxx.games.chastity.verification.VerificationVoteEntity;
|
|
||||||
import de.oaa.xxx.games.chastity.verification.VerificationVoteRepository;
|
|
||||||
import de.oaa.xxx.games.chastity.CodeCreator;
|
|
||||||
import de.oaa.xxx.games.chastity.verification.VerificationEntity;
|
|
||||||
import de.oaa.xxx.social.entity.MessageEntity;
|
|
||||||
import de.oaa.xxx.social.repository.MessageRepository;
|
|
||||||
import de.oaa.xxx.user.UserRepository;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.RenderingHints;
|
import java.awt.RenderingHints;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
@@ -42,6 +20,39 @@ import java.util.Map;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
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.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import de.oaa.xxx.games.chastity.CodeCreator;
|
||||||
|
import de.oaa.xxx.games.chastity.KeyholderInvitationEntity;
|
||||||
|
import de.oaa.xxx.games.chastity.KeyholderInvitationRepository;
|
||||||
|
import de.oaa.xxx.games.chastity.LockeeInvitationEntity;
|
||||||
|
import de.oaa.xxx.games.chastity.LockeeInvitationRepository;
|
||||||
|
import de.oaa.xxx.games.chastity.tasks.AssignedTaskEntity;
|
||||||
|
import de.oaa.xxx.games.chastity.tasks.AssignedTaskRepository;
|
||||||
|
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||||
|
import de.oaa.xxx.games.chastity.verification.VerificationEntity;
|
||||||
|
import de.oaa.xxx.games.chastity.verification.VerificationRepository;
|
||||||
|
import de.oaa.xxx.games.chastity.verification.VerificationVoteEntity;
|
||||||
|
import de.oaa.xxx.games.chastity.verification.VerificationVoteRepository;
|
||||||
|
import de.oaa.xxx.social.SseService;
|
||||||
|
import de.oaa.xxx.social.entity.MessageEntity;
|
||||||
|
import de.oaa.xxx.social.repository.MessageRepository;
|
||||||
|
import de.oaa.xxx.user.UserRepository;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/keyholder")
|
@RequestMapping("/keyholder")
|
||||||
public class CardLockController {
|
public class CardLockController {
|
||||||
@@ -55,6 +66,11 @@ public class CardLockController {
|
|||||||
private final HygieneViolationRepository hygieneViolationRepository;
|
private final HygieneViolationRepository hygieneViolationRepository;
|
||||||
private final MessageRepository messageRepository;
|
private final MessageRepository messageRepository;
|
||||||
private final LockeeInvitationRepository lockeeInvitationRepository;
|
private final LockeeInvitationRepository lockeeInvitationRepository;
|
||||||
|
private final AssignedTaskRepository assignedTaskRepository;
|
||||||
|
private final KeyholderTaskChoiceRepository keyholderTaskChoiceRepository;
|
||||||
|
private final CommunityTaskVoteRepository communityTaskVoteRepository;
|
||||||
|
private final UnlockCodeHistoryRepository unlockCodeHistoryRepository;
|
||||||
|
private final SseService sseService;
|
||||||
|
|
||||||
@Value("${app.base-url:http://localhost:8080}")
|
@Value("${app.base-url:http://localhost:8080}")
|
||||||
private String baseUrl;
|
private String baseUrl;
|
||||||
@@ -67,16 +83,26 @@ public class CardLockController {
|
|||||||
VerificationVoteRepository verificationVoteRepository,
|
VerificationVoteRepository verificationVoteRepository,
|
||||||
HygieneViolationRepository hygieneViolationRepository,
|
HygieneViolationRepository hygieneViolationRepository,
|
||||||
MessageRepository messageRepository,
|
MessageRepository messageRepository,
|
||||||
LockeeInvitationRepository lockeeInvitationRepository) {
|
LockeeInvitationRepository lockeeInvitationRepository,
|
||||||
this.cardlockRepository = cardlockRepository;
|
AssignedTaskRepository assignedTaskRepository,
|
||||||
this.cardLockRepository = cardLockRepository;
|
KeyholderTaskChoiceRepository keyholderTaskChoiceRepository,
|
||||||
this.userRepository = userRepository;
|
CommunityTaskVoteRepository communityTaskVoteRepository,
|
||||||
this.invitationRepository = invitationRepository;
|
UnlockCodeHistoryRepository unlockCodeHistoryRepository,
|
||||||
this.verificationRepository = verificationRepository;
|
SseService sseService) {
|
||||||
this.verificationVoteRepository = verificationVoteRepository;
|
this.cardlockRepository = cardlockRepository;
|
||||||
this.hygieneViolationRepository = hygieneViolationRepository;
|
this.cardLockRepository = cardLockRepository;
|
||||||
this.messageRepository = messageRepository;
|
this.userRepository = userRepository;
|
||||||
this.lockeeInvitationRepository = lockeeInvitationRepository;
|
this.invitationRepository = invitationRepository;
|
||||||
|
this.verificationRepository = verificationRepository;
|
||||||
|
this.verificationVoteRepository = verificationVoteRepository;
|
||||||
|
this.hygieneViolationRepository = hygieneViolationRepository;
|
||||||
|
this.messageRepository = messageRepository;
|
||||||
|
this.lockeeInvitationRepository = lockeeInvitationRepository;
|
||||||
|
this.assignedTaskRepository = assignedTaskRepository;
|
||||||
|
this.keyholderTaskChoiceRepository = keyholderTaskChoiceRepository;
|
||||||
|
this.communityTaskVoteRepository = communityTaskVoteRepository;
|
||||||
|
this.unlockCodeHistoryRepository = unlockCodeHistoryRepository;
|
||||||
|
this.sseService = sseService;
|
||||||
}
|
}
|
||||||
|
|
||||||
record CreateCardLockRequest(
|
record CreateCardLockRequest(
|
||||||
@@ -94,7 +120,8 @@ public class CardLockController {
|
|||||||
List<Task> tasks,
|
List<Task> tasks,
|
||||||
boolean requiresVerification,
|
boolean requiresVerification,
|
||||||
boolean testLock,
|
boolean testLock,
|
||||||
Integer unlockCodeLines
|
Integer unlockCodeLines,
|
||||||
|
String taskCardMode
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private static final SecureRandom RNG = new SecureRandom();
|
private static final SecureRandom RNG = new SecureRandom();
|
||||||
@@ -143,6 +170,7 @@ public class CardLockController {
|
|||||||
lock.setTasks(req.tasks() != null ? req.tasks() : List.of());
|
lock.setTasks(req.tasks() != null ? req.tasks() : List.of());
|
||||||
lock.setRequiresVerification(req.requiresVerification());
|
lock.setRequiresVerification(req.requiresVerification());
|
||||||
lock.setTestLock(false);
|
lock.setTestLock(false);
|
||||||
|
lock.setTaskCardMode(req.taskCardMode() != null ? req.taskCardMode() : "RANDOM");
|
||||||
// startTime, unlockCode, unlockCodeLines left null until lockee accepts
|
// startTime, unlockCode, unlockCodeLines left null until lockee accepts
|
||||||
cardlockRepository.save(lock);
|
cardlockRepository.save(lock);
|
||||||
|
|
||||||
@@ -157,14 +185,9 @@ public class CardLockController {
|
|||||||
lockeeInvitationRepository.save(inv);
|
lockeeInvitationRepository.save(inv);
|
||||||
|
|
||||||
String lockName = req.name() != null && !req.name().isBlank() ? req.name() : "Unbenanntes Lock";
|
String lockName = req.name() != null && !req.name().isBlank() ? req.name() : "Unbenanntes Lock";
|
||||||
MessageEntity msg = new MessageEntity();
|
sendMessage(myId, lockee.getUserId(),
|
||||||
msg.setMessageId(UUID.randomUUID());
|
me.getName() + " hat dich als Lockee für das Lock „" + lockName + "\" eingeladen.",
|
||||||
msg.setSenderId(myId);
|
"/einladungen.html");
|
||||||
msg.setReceiverId(lockee.getUserId());
|
|
||||||
msg.setText(me.getName() + " hat dich als Lockee für das Lock „" + lockName + "\" eingeladen.\n\n" +
|
|
||||||
"Deine Einladungen findest du hier: " + baseUrl + "/einladungen.html");
|
|
||||||
msg.setSentAt(now);
|
|
||||||
messageRepository.save(msg);
|
|
||||||
|
|
||||||
return ResponseEntity.ok(Map.of(
|
return ResponseEntity.ok(Map.of(
|
||||||
"lockId", lock.getLockId().toString(),
|
"lockId", lock.getLockId().toString(),
|
||||||
@@ -194,6 +217,7 @@ public class CardLockController {
|
|||||||
lock.setTasks(req.tasks() != null ? req.tasks() : List.of());
|
lock.setTasks(req.tasks() != null ? req.tasks() : List.of());
|
||||||
lock.setRequiresVerification(req.requiresVerification());
|
lock.setRequiresVerification(req.requiresVerification());
|
||||||
lock.setTestLock(req.testLock());
|
lock.setTestLock(req.testLock());
|
||||||
|
lock.setTaskCardMode(req.taskCardMode() != null ? req.taskCardMode() : "RANDOM");
|
||||||
lock.setUnlockCodeLines(codeLines);
|
lock.setUnlockCodeLines(codeLines);
|
||||||
lock.setUnlockCode(unlockCode);
|
lock.setUnlockCode(unlockCode);
|
||||||
|
|
||||||
@@ -224,15 +248,9 @@ public class CardLockController {
|
|||||||
invitationRepository.save(inv);
|
invitationRepository.save(inv);
|
||||||
|
|
||||||
String lockName = req.name() != null && !req.name().isBlank() ? req.name() : "Unbenanntes Lock";
|
String lockName = req.name() != null && !req.name().isBlank() ? req.name() : "Unbenanntes Lock";
|
||||||
|
sendMessage(me.getUserId(), kh.getUserId(),
|
||||||
MessageEntity msg = new MessageEntity();
|
me.getName() + " hat dich als Keyholder*In für das Lock „" + lockName + "\" eingeladen.",
|
||||||
msg.setMessageId(UUID.randomUUID());
|
"/einladungen.html");
|
||||||
msg.setSenderId(me.getUserId());
|
|
||||||
msg.setReceiverId(kh.getUserId());
|
|
||||||
msg.setText(me.getName() + " hat dich als Keyholder*In für das Lock „" + lockName + "\" eingeladen.\n\n" +
|
|
||||||
"Deine Einladungen findest du hier: " + baseUrl + "/einladungen.html");
|
|
||||||
msg.setSentAt(now);
|
|
||||||
messageRepository.save(msg);
|
|
||||||
|
|
||||||
keyholderPending = true;
|
keyholderPending = true;
|
||||||
}
|
}
|
||||||
@@ -261,9 +279,45 @@ public class CardLockController {
|
|||||||
CardDTO dto = service.getNextCard();
|
CardDTO dto = service.getNextCard();
|
||||||
if (dto == null) return ResponseEntity.status(409).body(Map.of("error", "Keine Karte verfügbar"));
|
if (dto == null) return ResponseEntity.status(409).body(Map.of("error", "Keine Karte verfügbar"));
|
||||||
|
|
||||||
|
// Task-Karte in nicht-zufälligem Modus → Entscheidung delegieren
|
||||||
|
String taskPending = null;
|
||||||
|
if (dto.card() == CardEnum.TASK && !"RANDOM".equals(l.getTaskCardMode())
|
||||||
|
&& l.getTasks() != null && !l.getTasks().isEmpty()) {
|
||||||
|
|
||||||
|
if ("KEYHOLDER".equals(l.getTaskCardMode()) && l.getKeyholder() != null) {
|
||||||
|
KeyholderTaskChoiceEntity choice = new KeyholderTaskChoiceEntity();
|
||||||
|
choice.setLockId(l.getLockId());
|
||||||
|
choice.setCreatedAt(LocalDateTime.now());
|
||||||
|
choice.setStatus("PENDING");
|
||||||
|
keyholderTaskChoiceRepository.save(choice);
|
||||||
|
userRepository.findById(l.getKeyholder()).ifPresent(kh ->
|
||||||
|
sendMessage(l.getLockee(), kh.getUserId(),
|
||||||
|
"Deine Lockee hat eine Aufgaben-Karte gezogen – wähle eine Aufgabe aus.",
|
||||||
|
"/keyholder.html"));
|
||||||
|
taskPending = "KEYHOLDER";
|
||||||
|
|
||||||
|
} else if ("COMMUNITY".equals(l.getTaskCardMode())) {
|
||||||
|
CommunityTaskVoteEntity vote = new CommunityTaskVoteEntity();
|
||||||
|
vote.setLockId(l.getLockId());
|
||||||
|
vote.setCreatedAt(LocalDateTime.now());
|
||||||
|
vote.setExpiresAt(LocalDateTime.now().plusHours(1));
|
||||||
|
vote.setStatus("ACTIVE");
|
||||||
|
vote.setTestLock(l.isTestLock());
|
||||||
|
communityTaskVoteRepository.save(vote);
|
||||||
|
taskPending = l.isTestLock() ? "RANDOM" : "COMMUNITY";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
result.put("card", dto.card().name());
|
result.put("card", dto.card().name());
|
||||||
result.put("unlockCode", dto.unlockCode() != null ? dto.unlockCode() : "");
|
result.put("unlockCode", dto.unlockCode() != null ? dto.unlockCode() : "");
|
||||||
|
if (taskPending != null) result.put("taskPending", taskPending);
|
||||||
|
|
||||||
|
// Grüne Karte → Entsperrcode-Historie speichern
|
||||||
|
if (dto.unlockCode() != null && !dto.unlockCode().isBlank()) {
|
||||||
|
saveUnlockCodeHistory(myId, l.getLockId(), l.getName(), dto.unlockCode(), "GREEN_CARD");
|
||||||
|
}
|
||||||
|
|
||||||
return ResponseEntity.ok(result);
|
return ResponseEntity.ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,6 +336,8 @@ public class CardLockController {
|
|||||||
l.setHygineOpeningtime(LocalDateTime.now());
|
l.setHygineOpeningtime(LocalDateTime.now());
|
||||||
cardlockRepository.save(l);
|
cardlockRepository.save(l);
|
||||||
|
|
||||||
|
saveUnlockCodeHistory(myId, l.getLockId(), l.getName(), l.getUnlockCode(), "HYGIENE_OPEN");
|
||||||
|
|
||||||
return ResponseEntity.ok(Map.of(
|
return ResponseEntity.ok(Map.of(
|
||||||
"unlockCode", l.getUnlockCode(),
|
"unlockCode", l.getUnlockCode(),
|
||||||
"durationMinutes", l.getHygineOpeningDurationMinutes() != null ? l.getHygineOpeningDurationMinutes() : 30
|
"durationMinutes", l.getHygineOpeningDurationMinutes() != null ? l.getHygineOpeningDurationMinutes() : 30
|
||||||
@@ -309,7 +365,11 @@ public class CardLockController {
|
|||||||
long overtimeMinutes = ChronoUnit.MINUTES.between(dueTime, now);
|
long overtimeMinutes = ChronoUnit.MINUTES.between(dueTime, now);
|
||||||
if (l.getKeyholder() == null) {
|
if (l.getKeyholder() == null) {
|
||||||
// Self-Lock: 4-fache Überschreitungszeit einfrieren
|
// Self-Lock: 4-fache Überschreitungszeit einfrieren
|
||||||
l.setFrozenUntill(now.plusMinutes(overtimeMinutes * 4));
|
if (l.getFrozenUntill() != null) {
|
||||||
|
l.setFrozenUntill(l.getFrozenUntill().plusMinutes(overtimeMinutes * 4));
|
||||||
|
} else {
|
||||||
|
l.setFrozenUntill(now.plusMinutes(overtimeMinutes * 4));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Keyholder vorhanden: Verletzung protokollieren
|
// Keyholder vorhanden: Verletzung protokollieren
|
||||||
HygieneViolationEntity violation = new HygieneViolationEntity();
|
HygieneViolationEntity violation = new HygieneViolationEntity();
|
||||||
@@ -333,6 +393,8 @@ public class CardLockController {
|
|||||||
l.setUnlockCode(newCode);
|
l.setUnlockCode(newCode);
|
||||||
cardlockRepository.save(l);
|
cardlockRepository.save(l);
|
||||||
|
|
||||||
|
saveUnlockCodeHistory(myId, l.getLockId(), l.getName(), newCode, "HYGIENE_CLOSE");
|
||||||
|
|
||||||
return ResponseEntity.ok(Map.of("newUnlockCode", newCode));
|
return ResponseEntity.ok(Map.of("newUnlockCode", newCode));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -422,8 +484,10 @@ public class CardLockController {
|
|||||||
result.put("totalCards", totalCards);
|
result.put("totalCards", totalCards);
|
||||||
result.put("openPicks", l.getOpenPicks() != null ? l.getOpenPicks() : 0);
|
result.put("openPicks", l.getOpenPicks() != null ? l.getOpenPicks() : 0);
|
||||||
result.put("nextCardIn", l.getNextCardIn() != null ? l.getNextCardIn().toString() : "");
|
result.put("nextCardIn", l.getNextCardIn() != null ? l.getNextCardIn().toString() : "");
|
||||||
result.put("frozenUntill", l.getFrozenUntill() != null ? l.getFrozenUntill().toString() : null);
|
result.put("frozenUntill", l.getFrozenUntill() != null ? l.getFrozenUntill().toString() : null);
|
||||||
result.put("currentTask", l.getCurrentTask() != null ? l.getCurrentTask() : null);
|
result.put("currentTask", l.getCurrentTask() != null ? l.getCurrentTask() : null);
|
||||||
|
result.put("currentTaskDescription", l.getCurrentTaskDescription());
|
||||||
|
result.put("taskFrozenUntil", l.getTaskFrozenUntil() != null ? l.getTaskFrozenUntil().toString() : null);
|
||||||
result.put("hygieneEnabled", hygieneEnabled);
|
result.put("hygieneEnabled", hygieneEnabled);
|
||||||
result.put("hygieneOpeningDue", hygieneOpeningDue);
|
result.put("hygieneOpeningDue", hygieneOpeningDue);
|
||||||
result.put("hygieneSecondsRemaining", hygieneSecondsRemaining);
|
result.put("hygieneSecondsRemaining", hygieneSecondsRemaining);
|
||||||
@@ -475,6 +539,79 @@ public class CardLockController {
|
|||||||
result.put("verificationPendingId", verificationPendingId);
|
result.put("verificationPendingId", verificationPendingId);
|
||||||
result.put("verificationPendingCode", verificationPendingCode);
|
result.put("verificationPendingCode", verificationPendingCode);
|
||||||
|
|
||||||
|
// Abgelaufene Aufgaben prüfen und Strafe anwenden
|
||||||
|
boolean lockDirty = false;
|
||||||
|
var expiredTasks = assignedTaskRepository.findByLockIdAndStatus(l.getLockId(), "PENDING")
|
||||||
|
.stream().filter(t -> t.getAcceptDeadline().isBefore(LocalDateTime.now())).toList();
|
||||||
|
for (var t : expiredTasks) {
|
||||||
|
t.setStatus("EXPIRED");
|
||||||
|
applyAssignedTaskPenalty(l, t);
|
||||||
|
assignedTaskRepository.save(t);
|
||||||
|
lockDirty = true;
|
||||||
|
sendMessage(l.getKeyholder(), l.getLockee(),
|
||||||
|
"Die dir gestellte Aufgabe ist abgelaufen, ohne dass du reagiert hast. Die Strafe wurde automatisch angewendet.",
|
||||||
|
"/activelock.html?lockId=" + l.getLockId());
|
||||||
|
}
|
||||||
|
if (lockDirty) cardlockRepository.save(l);
|
||||||
|
|
||||||
|
// Ausstehende Keyholder-Aufgaben (ohne Aufgabentext)
|
||||||
|
var pendingAssigned = assignedTaskRepository.findByLockIdAndStatus(l.getLockId(), "PENDING")
|
||||||
|
.stream()
|
||||||
|
.filter(t -> t.getAcceptDeadline().isAfter(LocalDateTime.now()))
|
||||||
|
.map(t -> {
|
||||||
|
Map<String, Object> m = new LinkedHashMap<>();
|
||||||
|
m.put("taskId", t.getTaskId().toString());
|
||||||
|
m.put("taskTitle", t.getTaskTitle() != null ? t.getTaskTitle() : t.getTaskText());
|
||||||
|
m.put("taskDescription", t.getTaskDescription() != null ? t.getTaskDescription() : "");
|
||||||
|
m.put("taskMinutes", t.getTaskMinutes() != null ? t.getTaskMinutes() : 0);
|
||||||
|
m.put("assignedAt", t.getAssignedAt().toString());
|
||||||
|
m.put("acceptDeadline", t.getAcceptDeadline().toString());
|
||||||
|
m.put("penaltyFreezeMinutes", t.getPenaltyFreezeMinutes() != null ? t.getPenaltyFreezeMinutes() : 0);
|
||||||
|
m.put("penaltyRedCards", t.getPenaltyRedCards() != null ? t.getPenaltyRedCards() : 0);
|
||||||
|
return m;
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
result.put("assignedTasks", pendingAssigned);
|
||||||
|
result.put("taskCardMode", l.getTaskCardMode());
|
||||||
|
|
||||||
|
// Ausstehende Keyholder-Choices
|
||||||
|
boolean pendingKeyholderChoice = !keyholderTaskChoiceRepository
|
||||||
|
.findByLockIdAndStatus(l.getLockId(), "PENDING").isEmpty();
|
||||||
|
result.put("pendingKeyholderChoice", pendingKeyholderChoice);
|
||||||
|
|
||||||
|
// Aktive Community-Vote
|
||||||
|
var activeVotes = communityTaskVoteRepository.findByStatus("ACTIVE").stream()
|
||||||
|
.filter(v -> v.getLockId().equals(l.getLockId())).findFirst();
|
||||||
|
if (activeVotes.isPresent()) {
|
||||||
|
var v = activeVotes.get();
|
||||||
|
result.put("activeCommunityVote", Map.of(
|
||||||
|
"voteSessionId", v.getVoteSessionId().toString(),
|
||||||
|
"expiresAt", v.getExpiresAt().toString()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notfall-Entsperrung: nach 1 Stunde automatisch öffnen
|
||||||
|
if (l.getEmergencyUnlockRequestedAt() != null
|
||||||
|
&& !l.isKeyholderRequestedUnlock()
|
||||||
|
&& l.getEmergencyUnlockRequestedAt().isBefore(LocalDateTime.now().minusHours(1))) {
|
||||||
|
l.setEmergencyAutoUnlocked(true);
|
||||||
|
l.setKeyholderRequestedUnlock(true);
|
||||||
|
cardlockRepository.save(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keyholder hat Unlock angefordert → Unlock-Code mitliefern
|
||||||
|
result.put("keyholderRequestedUnlock", l.isKeyholderRequestedUnlock());
|
||||||
|
if (l.isKeyholderRequestedUnlock()) {
|
||||||
|
result.put("unlockCode", l.getUnlockCode() != null ? l.getUnlockCode() : "");
|
||||||
|
saveUnlockCodeHistory(myId, l.getLockId(), l.getName(), l.getUnlockCode(), "KEYHOLDER_UNLOCK");
|
||||||
|
}
|
||||||
|
|
||||||
|
result.put("testLock", l.isTestLock());
|
||||||
|
result.put("emergencyUnlockRequested", l.getEmergencyUnlockRequestedAt() != null);
|
||||||
|
if (l.isTestLock()) {
|
||||||
|
result.put("unlockCode", l.getUnlockCode() != null ? l.getUnlockCode() : "");
|
||||||
|
}
|
||||||
|
|
||||||
return ResponseEntity.ok(result);
|
return ResponseEntity.ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -643,6 +780,7 @@ public class CardLockController {
|
|||||||
msg.setReceiverId(lock.getLockee());
|
msg.setReceiverId(lock.getLockee());
|
||||||
msg.setText(me.getName() + " hat die Einladung als Keyholder*In für das Lock „" + lockName + "\" abgelehnt.");
|
msg.setText(me.getName() + " hat die Einladung als Keyholder*In für das Lock „" + lockName + "\" abgelehnt.");
|
||||||
msg.setSentAt(LocalDateTime.now());
|
msg.setSentAt(LocalDateTime.now());
|
||||||
|
msg.setSystemMessage(true);
|
||||||
messageRepository.save(msg);
|
messageRepository.save(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,6 +842,7 @@ public class CardLockController {
|
|||||||
msg.setReceiverId(inv.getKeyholderUserId());
|
msg.setReceiverId(inv.getKeyholderUserId());
|
||||||
msg.setText(me.getName() + " hat die Keyholder-Einladung für das Lock „" + lockName + "\" zurückgezogen.");
|
msg.setText(me.getName() + " hat die Keyholder-Einladung für das Lock „" + lockName + "\" zurückgezogen.");
|
||||||
msg.setSentAt(LocalDateTime.now());
|
msg.setSentAt(LocalDateTime.now());
|
||||||
|
msg.setSystemMessage(true);
|
||||||
messageRepository.save(msg);
|
messageRepository.save(msg);
|
||||||
|
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
@@ -727,8 +866,12 @@ public class CardLockController {
|
|||||||
item.put("lockeeName", lockee.getName());
|
item.put("lockeeName", lockee.getName());
|
||||||
item.put("lockeeId", lockee.getUserId().toString());
|
item.put("lockeeId", lockee.getUserId().toString());
|
||||||
item.put("lockeeProfilePic", lockee.getProfilePicture());
|
item.put("lockeeProfilePic", lockee.getProfilePicture());
|
||||||
item.put("totalCards", lock.getAvailableCards() != null ? lock.getAvailableCards().size() : 0);
|
item.put("totalCards", lock.getAvailableCards() != null ? lock.getAvailableCards().size() : 0);
|
||||||
item.put("startTime", lock.getStartTime() != null ? lock.getStartTime().toString() : null);
|
item.put("startTime", lock.getStartTime() != null ? lock.getStartTime().toString() : null);
|
||||||
|
boolean frozenByKh = lock.getFrozenUntill() != null
|
||||||
|
&& lock.getFrozenUntill().isAfter(LocalDateTime.now())
|
||||||
|
&& (lock.getCurrentTask() == null || lock.getCurrentTask().isBlank());
|
||||||
|
item.put("isFrozenByKeyholder", frozenByKh);
|
||||||
result.add(item);
|
result.add(item);
|
||||||
}
|
}
|
||||||
return ResponseEntity.ok(result);
|
return ResponseEntity.ok(result);
|
||||||
@@ -811,7 +954,11 @@ public class CardLockController {
|
|||||||
result.put("openPicks", l.getOpenPicks() != null ? l.getOpenPicks() : 0);
|
result.put("openPicks", l.getOpenPicks() != null ? l.getOpenPicks() : 0);
|
||||||
result.put("nextCardIn", l.getNextCardIn() != null ? l.getNextCardIn().toString() : null);
|
result.put("nextCardIn", l.getNextCardIn() != null ? l.getNextCardIn().toString() : null);
|
||||||
result.put("frozenUntill", l.getFrozenUntill() != null ? l.getFrozenUntill().toString() : null);
|
result.put("frozenUntill", l.getFrozenUntill() != null ? l.getFrozenUntill().toString() : null);
|
||||||
|
result.put("taskFrozenUntil", l.getTaskFrozenUntil() != null ? l.getTaskFrozenUntil().toString() : null);
|
||||||
|
boolean isFrozenByKeyholder = l.getFrozenUntill() != null && l.getFrozenUntill().isAfter(LocalDateTime.now());
|
||||||
|
result.put("isFrozenByKeyholder", isFrozenByKeyholder);
|
||||||
result.put("currentTask", l.getCurrentTask());
|
result.put("currentTask", l.getCurrentTask());
|
||||||
|
result.put("currentTaskDescription", l.getCurrentTaskDescription());
|
||||||
result.put("startTime", l.getStartTime() != null ? l.getStartTime().toString() : null);
|
result.put("startTime", l.getStartTime() != null ? l.getStartTime().toString() : null);
|
||||||
result.put("hygieneEnabled", hygieneEnabled);
|
result.put("hygieneEnabled", hygieneEnabled);
|
||||||
result.put("hygieneOpeningDue", hygieneOpeningDue);
|
result.put("hygieneOpeningDue", hygieneOpeningDue);
|
||||||
@@ -827,6 +974,66 @@ public class CardLockController {
|
|||||||
result.put("verificationDownvotes", verificationDownvotes);
|
result.put("verificationDownvotes", verificationDownvotes);
|
||||||
result.put("hygieneViolations", recentViolations);
|
result.put("hygieneViolations", recentViolations);
|
||||||
result.put("hasTasks", l.getTasks() != null && !l.getTasks().isEmpty());
|
result.put("hasTasks", l.getTasks() != null && !l.getTasks().isEmpty());
|
||||||
|
if (l.getTasks() != null) {
|
||||||
|
var taskList = l.getTasks().stream()
|
||||||
|
.map(t -> {
|
||||||
|
Map<String, Object> m = new LinkedHashMap<>();
|
||||||
|
m.put("title", t.resolveTitle());
|
||||||
|
m.put("description", t.getDescription() != null ? t.getDescription() : "");
|
||||||
|
m.put("minutes", t.getMinutes() != null ? t.getMinutes() : 0);
|
||||||
|
return m;
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
result.put("taskList", taskList);
|
||||||
|
} else {
|
||||||
|
result.put("taskList", List.of());
|
||||||
|
}
|
||||||
|
var pendingAssigned = assignedTaskRepository.findByLockIdAndStatus(lockId, "PENDING")
|
||||||
|
.stream()
|
||||||
|
.filter(t -> t.getAcceptDeadline().isAfter(LocalDateTime.now()))
|
||||||
|
.map(t -> {
|
||||||
|
Map<String, Object> m = new LinkedHashMap<>();
|
||||||
|
m.put("taskId", t.getTaskId().toString());
|
||||||
|
m.put("taskTitle", t.getTaskTitle() != null ? t.getTaskTitle() : t.getTaskText());
|
||||||
|
m.put("taskDescription", t.getTaskDescription() != null ? t.getTaskDescription() : "");
|
||||||
|
m.put("taskMinutes", t.getTaskMinutes() != null ? t.getTaskMinutes() : 0);
|
||||||
|
m.put("assignedAt", t.getAssignedAt().toString());
|
||||||
|
m.put("acceptDeadline", t.getAcceptDeadline().toString());
|
||||||
|
m.put("penaltyFreezeMinutes", t.getPenaltyFreezeMinutes() != null ? t.getPenaltyFreezeMinutes() : 0);
|
||||||
|
m.put("penaltyRedCards", t.getPenaltyRedCards() != null ? t.getPenaltyRedCards() : 0);
|
||||||
|
return m;
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
result.put("pendingAssignedTasks", pendingAssigned);
|
||||||
|
result.put("taskCardMode", l.getTaskCardMode());
|
||||||
|
|
||||||
|
// Ausstehende Task-Karten-Choices (KEYHOLDER-Modus)
|
||||||
|
List<Task> lockTasks = l.getTasks() != null ? l.getTasks() : List.of();
|
||||||
|
List<Map<String, Object>> taskListForChoice = new ArrayList<>();
|
||||||
|
for (int i = 0; i < lockTasks.size(); i++) {
|
||||||
|
Task t = lockTasks.get(i);
|
||||||
|
Map<String, Object> tm = new LinkedHashMap<>();
|
||||||
|
tm.put("index", i);
|
||||||
|
tm.put("title", t.resolveTitle());
|
||||||
|
tm.put("description", t.getDescription() != null ? t.getDescription() : "");
|
||||||
|
tm.put("minutes", t.getMinutes() != null ? t.getMinutes() : 0);
|
||||||
|
taskListForChoice.add(tm);
|
||||||
|
}
|
||||||
|
var pendingChoices = keyholderTaskChoiceRepository.findByLockIdAndStatus(lockId, "PENDING")
|
||||||
|
.stream()
|
||||||
|
.map(c -> {
|
||||||
|
Map<String, Object> cm = new LinkedHashMap<>();
|
||||||
|
cm.put("choiceId", c.getChoiceId().toString());
|
||||||
|
cm.put("createdAt", c.getCreatedAt().toString());
|
||||||
|
cm.put("tasks", taskListForChoice);
|
||||||
|
return cm;
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
result.put("pendingTaskChoices", pendingChoices);
|
||||||
|
result.put("keyholderRequestedUnlock", l.isKeyholderRequestedUnlock());
|
||||||
|
result.put("emergencyUnlockRequested", l.getEmergencyUnlockRequestedAt() != null);
|
||||||
|
result.put("emergencyUnlockRequestedAt", l.getEmergencyUnlockRequestedAt() != null ? l.getEmergencyUnlockRequestedAt().toString() : null);
|
||||||
|
|
||||||
return ResponseEntity.ok(result);
|
return ResponseEntity.ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -899,6 +1106,7 @@ public class CardLockController {
|
|||||||
msg.setReceiverId(l.getLockee());
|
msg.setReceiverId(l.getLockee());
|
||||||
msg.setText(msgText);
|
msg.setText(msgText);
|
||||||
msg.setSentAt(LocalDateTime.now());
|
msg.setSentAt(LocalDateTime.now());
|
||||||
|
msg.setSystemMessage(true);
|
||||||
messageRepository.save(msg);
|
messageRepository.save(msg);
|
||||||
|
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
@@ -963,11 +1171,383 @@ public class CardLockController {
|
|||||||
msg.setReceiverId(l.getLockee());
|
msg.setReceiverId(l.getLockee());
|
||||||
msg.setText(msgText);
|
msg.setText(msgText);
|
||||||
msg.setSentAt(LocalDateTime.now());
|
msg.setSentAt(LocalDateTime.now());
|
||||||
|
msg.setSystemMessage(true);
|
||||||
messageRepository.save(msg);
|
messageRepository.save(msg);
|
||||||
|
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Hilfsmethoden ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void applyAssignedTaskPenalty(CardLockEntity l, AssignedTaskEntity task) {
|
||||||
|
if (task.getPenaltyFreezeMinutes() != null && task.getPenaltyFreezeMinutes() > 0) {
|
||||||
|
LocalDateTime until = LocalDateTime.now().plusMinutes(task.getPenaltyFreezeMinutes());
|
||||||
|
// Bestehenden Freeze nur verlängern, nie verkürzen
|
||||||
|
if (l.getFrozenUntill() == null || until.isAfter(l.getFrozenUntill())) {
|
||||||
|
l.setFrozenUntill(until);
|
||||||
|
l.setNextCardIn(until);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (task.getPenaltyRedCards() != null && task.getPenaltyRedCards() > 0) {
|
||||||
|
List<CardEnum> cards = new ArrayList<>(l.getAvailableCards() != null ? l.getAvailableCards() : List.of());
|
||||||
|
for (int i = 0; i < task.getPenaltyRedCards(); i++) {
|
||||||
|
cards.add(CardEnum.RED);
|
||||||
|
}
|
||||||
|
l.setAvailableCards(cards);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessage(UUID senderId, UUID receiverId, String text, String targetUrl) {
|
||||||
|
if (senderId == null || receiverId == null) return;
|
||||||
|
MessageEntity msg = new MessageEntity();
|
||||||
|
msg.setMessageId(UUID.randomUUID());
|
||||||
|
msg.setSenderId(senderId);
|
||||||
|
msg.setReceiverId(receiverId);
|
||||||
|
msg.setText(text);
|
||||||
|
msg.setSentAt(LocalDateTime.now());
|
||||||
|
msg.setSystemMessage(true);
|
||||||
|
if (targetUrl != null) msg.setTargetUrl(targetUrl);
|
||||||
|
messageRepository.save(msg);
|
||||||
|
long unread = messageRepository.countByReceiverIdAndSystemMessageAndReadAtIsNull(receiverId, true);
|
||||||
|
sseService.push(receiverId, "NOTIFICATION", java.util.Map.of("unreadCount", unread, "text", text));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Entsperrcode-Historie ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void saveUnlockCodeHistory(UUID userId, UUID lockId, String lockName, String unlockCode, String source) {
|
||||||
|
if (unlockCode == null || unlockCode.isBlank()) return;
|
||||||
|
// Deduplizierung: gleicher Code+Quelle+Lock bereits gespeichert → überspringen
|
||||||
|
if (unlockCodeHistoryRepository.existsByLockIdAndSourceAndUnlockCode(lockId, source, unlockCode)) return;
|
||||||
|
UnlockCodeHistoryEntity entry = new UnlockCodeHistoryEntity();
|
||||||
|
entry.setUserId(userId);
|
||||||
|
entry.setLockId(lockId);
|
||||||
|
entry.setLockName(lockName != null && !lockName.isBlank() ? lockName : "Unbenanntes Lock");
|
||||||
|
entry.setUnlockCode(unlockCode);
|
||||||
|
entry.setSource(source);
|
||||||
|
entry.setReceivedAt(LocalDateTime.now());
|
||||||
|
unlockCodeHistoryRepository.save(entry);
|
||||||
|
// Nur die letzten 10 behalten
|
||||||
|
long count = unlockCodeHistoryRepository.countByUserId(userId);
|
||||||
|
if (count > 10) {
|
||||||
|
var oldest = unlockCodeHistoryRepository.findByUserIdOrderByReceivedAtAsc(userId);
|
||||||
|
for (int i = 0; i < count - 10; i++) {
|
||||||
|
unlockCodeHistoryRepository.delete(oldest.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/cardlock/unlock-history")
|
||||||
|
public ResponseEntity<List<Map<String, Object>>> getUnlockHistory(Principal principal) {
|
||||||
|
var meOpt = userRepository.findByEmail(principal.getName());
|
||||||
|
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||||
|
UUID myId = meOpt.get().getUserId();
|
||||||
|
var entries = unlockCodeHistoryRepository.findByUserIdOrderByReceivedAtDesc(myId,
|
||||||
|
org.springframework.data.domain.PageRequest.of(0, 10));
|
||||||
|
List<Map<String, Object>> result = new ArrayList<>();
|
||||||
|
for (var e : entries) {
|
||||||
|
Map<String, Object> item = new HashMap<>();
|
||||||
|
item.put("lockName", e.getLockName());
|
||||||
|
item.put("unlockCode", e.getUnlockCode());
|
||||||
|
item.put("source", e.getSource());
|
||||||
|
item.put("receivedAt", e.getReceivedAt().toString());
|
||||||
|
result.add(item);
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Keyholder: Aufgabe stellen ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
record AssignTaskRequest(int taskIndex, int acceptDeadlineMinutes,
|
||||||
|
Integer penaltyFreezeMinutes, Integer penaltyRedCards) {}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@PostMapping("/as-keyholder/{lockId}/assign-task")
|
||||||
|
public ResponseEntity<?> assignTask(@PathVariable UUID lockId,
|
||||||
|
@RequestBody AssignTaskRequest req,
|
||||||
|
Principal principal) {
|
||||||
|
var meOpt = userRepository.findByEmail(principal.getName());
|
||||||
|
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||||
|
var me = meOpt.get();
|
||||||
|
|
||||||
|
var lockOpt = cardlockRepository.findById(lockId);
|
||||||
|
if (lockOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||||
|
var l = lockOpt.get();
|
||||||
|
if (!me.getUserId().equals(l.getKeyholder())) return ResponseEntity.status(403).build();
|
||||||
|
|
||||||
|
var tasks = l.getTasks();
|
||||||
|
if (tasks == null || tasks.isEmpty())
|
||||||
|
return ResponseEntity.badRequest().body(Map.of("error", "Dieses Lock hat keine Aufgaben."));
|
||||||
|
if (req.taskIndex() < 0 || req.taskIndex() >= tasks.size())
|
||||||
|
return ResponseEntity.badRequest().body(Map.of("error", "Ungültiger Aufgaben-Index."));
|
||||||
|
if (req.acceptDeadlineMinutes() < 1)
|
||||||
|
return ResponseEntity.badRequest().body(Map.of("error", "Die Annahme-Frist muss mindestens 1 Minute betragen."));
|
||||||
|
|
||||||
|
long pendingCount = assignedTaskRepository.findByLockIdAndStatus(lockId, "PENDING").stream()
|
||||||
|
.filter(t -> t.getAcceptDeadline().isAfter(LocalDateTime.now()))
|
||||||
|
.count();
|
||||||
|
if (pendingCount >= 5)
|
||||||
|
return ResponseEntity.badRequest().body(Map.of("error", "Es sind bereits 5 Aufgaben offen. Bitte warte, bis der Lockee eine davon annimmt oder ablehnt."));
|
||||||
|
|
||||||
|
Task task = tasks.get(req.taskIndex());
|
||||||
|
AssignedTaskEntity assigned = new AssignedTaskEntity();
|
||||||
|
assigned.setLockId(lockId);
|
||||||
|
assigned.setTaskTitle(task.resolveTitle());
|
||||||
|
assigned.setTaskDescription(task.getDescription());
|
||||||
|
assigned.setTaskText(task.resolveTitle()); // Compat
|
||||||
|
assigned.setTaskMinutes(task.getMinutes());
|
||||||
|
assigned.setAssignedAt(LocalDateTime.now());
|
||||||
|
assigned.setAcceptDeadline(LocalDateTime.now().plusMinutes(req.acceptDeadlineMinutes()));
|
||||||
|
assigned.setPenaltyFreezeMinutes(req.penaltyFreezeMinutes());
|
||||||
|
assigned.setPenaltyRedCards(req.penaltyRedCards());
|
||||||
|
assigned.setStatus("PENDING");
|
||||||
|
assignedTaskRepository.save(assigned);
|
||||||
|
|
||||||
|
sendMessage(me.getUserId(), l.getLockee(),
|
||||||
|
me.getName() + " hat dir eine Aufgabe gestellt. Du hast " +
|
||||||
|
req.acceptDeadlineMinutes() + " Minuten, um sie anzunehmen.",
|
||||||
|
"/activelock.html?lockId=" + lockId);
|
||||||
|
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Lockee: Aufgabe annehmen ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@PostMapping("/cardlock/{lockId}/assigned-tasks/{taskId}/accept")
|
||||||
|
public ResponseEntity<?> acceptAssignedTask(@PathVariable UUID lockId,
|
||||||
|
@PathVariable UUID taskId,
|
||||||
|
Principal principal) {
|
||||||
|
var meOpt = userRepository.findByEmail(principal.getName());
|
||||||
|
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||||
|
UUID myId = meOpt.get().getUserId();
|
||||||
|
|
||||||
|
var lockOpt = cardlockRepository.findById(lockId);
|
||||||
|
if (lockOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||||
|
var l = lockOpt.get();
|
||||||
|
if (!l.getLockee().equals(myId)) return ResponseEntity.status(403).build();
|
||||||
|
|
||||||
|
var taskOpt = assignedTaskRepository.findById(taskId);
|
||||||
|
if (taskOpt.isEmpty() || !taskOpt.get().getLockId().equals(lockId))
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
var task = taskOpt.get();
|
||||||
|
if (!"PENDING".equals(task.getStatus()))
|
||||||
|
return ResponseEntity.status(409).body(Map.of("error", "Diese Aufgabe ist nicht mehr ausstehend."));
|
||||||
|
if (task.getAcceptDeadline().isBefore(LocalDateTime.now())) {
|
||||||
|
// Bereits abgelaufen – Strafe anwenden
|
||||||
|
task.setStatus("EXPIRED");
|
||||||
|
applyAssignedTaskPenalty(l, task);
|
||||||
|
assignedTaskRepository.save(task);
|
||||||
|
cardlockRepository.save(l);
|
||||||
|
return ResponseEntity.status(409).body(Map.of("error", "Die Annahme-Frist ist abgelaufen. Die Strafe wurde angewendet."));
|
||||||
|
}
|
||||||
|
boolean hasActiveTask = (l.getCurrentTask() != null && !l.getCurrentTask().isBlank())
|
||||||
|
|| (l.getTaskFrozenUntil() != null && l.getTaskFrozenUntil().isAfter(LocalDateTime.now()));
|
||||||
|
if (hasActiveTask)
|
||||||
|
return ResponseEntity.status(409).body(Map.of("error", "Du hast bereits eine laufende Aufgabe."));
|
||||||
|
|
||||||
|
// Aufgabe aktivieren – separater Task-Timer, kein Freeze
|
||||||
|
String title = task.getTaskTitle() != null ? task.getTaskTitle() : task.getTaskText();
|
||||||
|
l.setCurrentTask(title);
|
||||||
|
l.setCurrentTaskDescription(task.getTaskDescription());
|
||||||
|
if (task.getTaskMinutes() != null && task.getTaskMinutes() > 0) {
|
||||||
|
l.setTaskFrozenUntil(LocalDateTime.now().plusMinutes(task.getTaskMinutes()));
|
||||||
|
|
||||||
|
// Fälligkeit aller anderen offenen Aufgaben um die Task-Dauer verschieben
|
||||||
|
final int extraMinutes = task.getTaskMinutes();
|
||||||
|
assignedTaskRepository.findByLockIdAndStatus(lockId, "PENDING").stream()
|
||||||
|
.filter(t -> !t.getTaskId().equals(taskId))
|
||||||
|
.forEach(t -> {
|
||||||
|
t.setAcceptDeadline(t.getAcceptDeadline().plusMinutes(extraMinutes));
|
||||||
|
assignedTaskRepository.save(t);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
task.setStatus("ACCEPTED");
|
||||||
|
assignedTaskRepository.save(task);
|
||||||
|
cardlockRepository.save(l);
|
||||||
|
|
||||||
|
sendMessage(myId, l.getKeyholder(), meOpt.get().getName() + " hat die gestellte Aufgabe angenommen.", "/keyholder.html");
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Lockee: Aufgabe ablehnen ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@PostMapping("/cardlock/{lockId}/assigned-tasks/{taskId}/decline")
|
||||||
|
public ResponseEntity<?> declineAssignedTask(@PathVariable UUID lockId,
|
||||||
|
@PathVariable UUID taskId,
|
||||||
|
Principal principal) {
|
||||||
|
var meOpt = userRepository.findByEmail(principal.getName());
|
||||||
|
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||||
|
UUID myId = meOpt.get().getUserId();
|
||||||
|
|
||||||
|
var lockOpt = cardlockRepository.findById(lockId);
|
||||||
|
if (lockOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||||
|
var l = lockOpt.get();
|
||||||
|
if (!l.getLockee().equals(myId)) return ResponseEntity.status(403).build();
|
||||||
|
|
||||||
|
var taskOpt = assignedTaskRepository.findById(taskId);
|
||||||
|
if (taskOpt.isEmpty() || !taskOpt.get().getLockId().equals(lockId))
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
var task = taskOpt.get();
|
||||||
|
if (!"PENDING".equals(task.getStatus()))
|
||||||
|
return ResponseEntity.status(409).body(Map.of("error", "Diese Aufgabe ist nicht mehr ausstehend."));
|
||||||
|
|
||||||
|
task.setStatus("DECLINED");
|
||||||
|
applyAssignedTaskPenalty(l, task);
|
||||||
|
assignedTaskRepository.save(task);
|
||||||
|
cardlockRepository.save(l);
|
||||||
|
|
||||||
|
sendMessage(myId, l.getKeyholder(), meOpt.get().getName() + " hat die gestellte Aufgabe abgelehnt. Die Strafe wurde angewendet.", "/keyholder.html");
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Keyholder: Aufgabe zurückziehen ───────────────────────────────────────
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@DeleteMapping("/as-keyholder/{lockId}/assigned-tasks/{taskId}")
|
||||||
|
public ResponseEntity<?> cancelAssignedTask(@PathVariable UUID lockId,
|
||||||
|
@PathVariable UUID taskId,
|
||||||
|
Principal principal) {
|
||||||
|
var meOpt = userRepository.findByEmail(principal.getName());
|
||||||
|
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||||
|
UUID myId = meOpt.get().getUserId();
|
||||||
|
|
||||||
|
var lockOpt = cardlockRepository.findById(lockId);
|
||||||
|
if (lockOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||||
|
var l = lockOpt.get();
|
||||||
|
if (!myId.equals(l.getKeyholder())) return ResponseEntity.status(403).build();
|
||||||
|
|
||||||
|
var taskOpt = assignedTaskRepository.findById(taskId);
|
||||||
|
if (taskOpt.isEmpty() || !taskOpt.get().getLockId().equals(lockId))
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
var task = taskOpt.get();
|
||||||
|
if (!"PENDING".equals(task.getStatus()))
|
||||||
|
return ResponseEntity.status(409).body(Map.of("error", "Aufgabe ist nicht mehr ausstehend."));
|
||||||
|
|
||||||
|
assignedTaskRepository.delete(task);
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
record FreezeRequest(String frozenUntil) {}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@PostMapping("/as-keyholder/{lockId}/freeze")
|
||||||
|
public ResponseEntity<?> freezeLock(@PathVariable UUID lockId,
|
||||||
|
@RequestBody FreezeRequest req,
|
||||||
|
Principal principal) {
|
||||||
|
var meOpt = userRepository.findByEmail(principal.getName());
|
||||||
|
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||||
|
var me = meOpt.get();
|
||||||
|
UUID myId = me.getUserId();
|
||||||
|
|
||||||
|
var lockOpt = cardlockRepository.findById(lockId);
|
||||||
|
if (lockOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||||
|
var l = lockOpt.get();
|
||||||
|
if (!myId.equals(l.getKeyholder())) return ResponseEntity.status(403).build();
|
||||||
|
if (l.getCurrentTask() != null && !l.getCurrentTask().isBlank()) {
|
||||||
|
return ResponseEntity.badRequest().body(Map.of("error", "Das Lock ist gerade durch eine Aufgabe eingefroren."));
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDateTime until;
|
||||||
|
try {
|
||||||
|
until = LocalDateTime.parse(req.frozenUntil());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ResponseEntity.badRequest().body(Map.of("error", "Ungültiges Datumsformat."));
|
||||||
|
}
|
||||||
|
if (!until.isAfter(LocalDateTime.now())) {
|
||||||
|
return ResponseEntity.badRequest().body(Map.of("error", "Zeitpunkt muss in der Zukunft liegen."));
|
||||||
|
}
|
||||||
|
|
||||||
|
l.setFrozenUntill(until);
|
||||||
|
cardlockRepository.save(l);
|
||||||
|
|
||||||
|
sendMessage(myId, l.getLockee(), me.getName() + " hat dein Lock bis " +
|
||||||
|
until.toLocalDate().format(java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy")) +
|
||||||
|
" " + until.toLocalTime().format(java.time.format.DateTimeFormatter.ofPattern("HH:mm")) +
|
||||||
|
" Uhr eingefroren.",
|
||||||
|
"/activelock.html?lockId=" + lockId);
|
||||||
|
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@DeleteMapping("/as-keyholder/{lockId}/freeze")
|
||||||
|
public ResponseEntity<?> unfreezeLock(@PathVariable UUID lockId, Principal principal) {
|
||||||
|
var meOpt = userRepository.findByEmail(principal.getName());
|
||||||
|
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||||
|
var me = meOpt.get();
|
||||||
|
UUID myId = me.getUserId();
|
||||||
|
|
||||||
|
var lockOpt = cardlockRepository.findById(lockId);
|
||||||
|
if (lockOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||||
|
var l = lockOpt.get();
|
||||||
|
if (!myId.equals(l.getKeyholder())) return ResponseEntity.status(403).build();
|
||||||
|
if (l.getCurrentTask() != null && !l.getCurrentTask().isBlank()) {
|
||||||
|
return ResponseEntity.badRequest().body(Map.of("error", "Das Lock ist durch eine Aufgabe eingefroren und kann nicht manuell entfroren werden."));
|
||||||
|
}
|
||||||
|
|
||||||
|
l.setFrozenUntill(null);
|
||||||
|
cardlockRepository.save(l);
|
||||||
|
|
||||||
|
sendMessage(myId, l.getLockee(), me.getName() + " hat dein Lock wieder entfroren.",
|
||||||
|
"/activelock.html?lockId=" + lockId);
|
||||||
|
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@PostMapping("/as-keyholder/{lockId}/request-unlock")
|
||||||
|
public ResponseEntity<?> requestUnlock(@PathVariable UUID lockId, Principal principal) {
|
||||||
|
var meOpt = userRepository.findByEmail(principal.getName());
|
||||||
|
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||||
|
UUID myId = meOpt.get().getUserId();
|
||||||
|
|
||||||
|
var lockOpt = cardlockRepository.findById(lockId);
|
||||||
|
if (lockOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||||
|
var l = lockOpt.get();
|
||||||
|
if (!myId.equals(l.getKeyholder())) return ResponseEntity.status(403).build();
|
||||||
|
|
||||||
|
l.setKeyholderRequestedUnlock(true);
|
||||||
|
cardlockRepository.save(l);
|
||||||
|
|
||||||
|
sendMessage(myId, l.getLockee(),
|
||||||
|
"Dein Keyholder hat das Lock freigeschaltet. Du erhältst beim nächsten Laden deinen Entsperrcode.",
|
||||||
|
"/activelock.html?lockId=" + lockId);
|
||||||
|
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@PostMapping("/cardlock/{lockId}/emergency-unlock")
|
||||||
|
public ResponseEntity<?> requestEmergencyUnlock(@PathVariable UUID lockId, Principal principal) {
|
||||||
|
var meOpt = userRepository.findByEmail(principal.getName());
|
||||||
|
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||||
|
var me = meOpt.get();
|
||||||
|
UUID myId = me.getUserId();
|
||||||
|
|
||||||
|
var lockOpt = cardlockRepository.findById(lockId);
|
||||||
|
if (lockOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||||
|
var l = lockOpt.get();
|
||||||
|
if (!l.getLockee().equals(myId)) return ResponseEntity.status(403).build();
|
||||||
|
if (l.isTestLock()) return ResponseEntity.badRequest().build();
|
||||||
|
if (l.getEmergencyUnlockRequestedAt() != null) return ResponseEntity.status(409).build();
|
||||||
|
|
||||||
|
l.setEmergencyUnlockRequestedAt(LocalDateTime.now());
|
||||||
|
|
||||||
|
if (l.getKeyholder() == null) {
|
||||||
|
// Self-Lock ohne Keyholderin → sofort öffnen
|
||||||
|
l.setEmergencyAutoUnlocked(true);
|
||||||
|
l.setKeyholderRequestedUnlock(true);
|
||||||
|
} else {
|
||||||
|
// Keyholderin benachrichtigen
|
||||||
|
sendMessage(myId, l.getKeyholder(),
|
||||||
|
"⚠️ NOTFALL: " + me.getName() + " bittet dringend um Freigabe des Locks. Bitte reagiere innerhalb einer Stunde, sonst öffnet sich das Lock automatisch.",
|
||||||
|
"/keyholder.html");
|
||||||
|
}
|
||||||
|
cardlockRepository.save(l);
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
private String cardLabel(CardEnum card) {
|
private String cardLabel(CardEnum card) {
|
||||||
return switch (card) {
|
return switch (card) {
|
||||||
case RED -> "Rote Karte";
|
case RED -> "Rote Karte";
|
||||||
|
|||||||
@@ -76,12 +76,32 @@ public class CardLockEntity {
|
|||||||
private LocalDateTime unlockTime;
|
private LocalDateTime unlockTime;
|
||||||
@Column
|
@Column
|
||||||
private String currentTask;
|
private String currentTask;
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
|
private String currentTaskDescription;
|
||||||
|
@Column
|
||||||
|
private LocalDateTime taskFrozenUntil;
|
||||||
@Convert(converter = TaskListConverter.class)
|
@Convert(converter = TaskListConverter.class)
|
||||||
@Column(columnDefinition = "TEXT")
|
@Column(columnDefinition = "TEXT")
|
||||||
private List<Task> tasksInQueue;
|
private List<Task> tasksInQueue;
|
||||||
@Column
|
@Column
|
||||||
private String unlockCode;
|
private String unlockCode;
|
||||||
|
|
||||||
|
/** Keyholder hat Unlock angefordert – nächste Aktion der Lockee zeigt grüne Karte */
|
||||||
|
@Column(nullable = false)
|
||||||
|
private boolean keyholderRequestedUnlock = false;
|
||||||
|
|
||||||
|
/** Lockee hat Notfall-Entsperrung angefordert */
|
||||||
|
@Column
|
||||||
|
private java.time.LocalDateTime emergencyUnlockRequestedAt;
|
||||||
|
|
||||||
|
/** true = System hat automatisch entsperrt (Keyholderin nicht reagiert) */
|
||||||
|
@Column(nullable = false)
|
||||||
|
private boolean emergencyAutoUnlocked = false;
|
||||||
|
|
||||||
|
/** RANDOM | KEYHOLDER | COMMUNITY */
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String taskCardMode = "RANDOM";
|
||||||
|
|
||||||
public UUID getLockId() {
|
public UUID getLockId() {
|
||||||
return lockId;
|
return lockId;
|
||||||
}
|
}
|
||||||
@@ -242,13 +262,14 @@ public class CardLockEntity {
|
|||||||
this.tasks = tasks;
|
this.tasks = tasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCurrentTask() {
|
public String getCurrentTask() { return currentTask; }
|
||||||
return currentTask;
|
public void setCurrentTask(String currentTask) { this.currentTask = currentTask; }
|
||||||
}
|
|
||||||
|
|
||||||
public void setCurrentTask(String currentTask) {
|
public String getCurrentTaskDescription() { return currentTaskDescription; }
|
||||||
this.currentTask = currentTask;
|
public void setCurrentTaskDescription(String d) { this.currentTaskDescription = d; }
|
||||||
}
|
|
||||||
|
public LocalDateTime getTaskFrozenUntil() { return taskFrozenUntil; }
|
||||||
|
public void setTaskFrozenUntil(LocalDateTime t) { this.taskFrozenUntil = t; }
|
||||||
|
|
||||||
public List<Task> getTasksInQueue() {
|
public List<Task> getTasksInQueue() {
|
||||||
return tasksInQueue;
|
return tasksInQueue;
|
||||||
@@ -289,4 +310,16 @@ public class CardLockEntity {
|
|||||||
public void setUnlockCodeLines(Integer unlockCodeLines) {
|
public void setUnlockCodeLines(Integer unlockCodeLines) {
|
||||||
this.unlockCodeLines = unlockCodeLines;
|
this.unlockCodeLines = unlockCodeLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTaskCardMode() { return taskCardMode != null ? taskCardMode : "RANDOM"; }
|
||||||
|
public void setTaskCardMode(String taskCardMode) { this.taskCardMode = taskCardMode; }
|
||||||
|
|
||||||
|
public boolean isKeyholderRequestedUnlock() { return keyholderRequestedUnlock; }
|
||||||
|
public void setKeyholderRequestedUnlock(boolean v) { this.keyholderRequestedUnlock = v; }
|
||||||
|
|
||||||
|
public java.time.LocalDateTime getEmergencyUnlockRequestedAt() { return emergencyUnlockRequestedAt; }
|
||||||
|
public void setEmergencyUnlockRequestedAt(java.time.LocalDateTime t) { this.emergencyUnlockRequestedAt = t; }
|
||||||
|
|
||||||
|
public boolean isEmergencyAutoUnlocked() { return emergencyAutoUnlocked; }
|
||||||
|
public void setEmergencyAutoUnlocked(boolean v) { this.emergencyAutoUnlocked = v; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package de.oaa.xxx.games.chastity;
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -11,10 +11,7 @@ import java.util.stream.Collectors;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import de.oaa.xxx.games.chastity.cardlock.CardDTO;
|
import de.oaa.xxx.games.chastity.ProcessLock;
|
||||||
import de.oaa.xxx.games.chastity.cardlock.CardEnum;
|
|
||||||
import de.oaa.xxx.games.chastity.cardlock.CardLockEntity;
|
|
||||||
import de.oaa.xxx.games.chastity.cardlock.CardLockRepository;
|
|
||||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||||
import de.oaa.xxx.games.chastity.verification.VerificationEntity;
|
import de.oaa.xxx.games.chastity.verification.VerificationEntity;
|
||||||
import de.oaa.xxx.games.chastity.verification.VerificationRepository;
|
import de.oaa.xxx.games.chastity.verification.VerificationRepository;
|
||||||
@@ -39,7 +36,9 @@ public class CardLockService extends ProcessLock {
|
|||||||
public CardDTO getNextCard() {
|
public CardDTO getNextCard() {
|
||||||
LOGGER.debug("New Card requested by user {}", lock.getLockee());
|
LOGGER.debug("New Card requested by user {}", lock.getLockee());
|
||||||
CardDTO card = null;
|
CardDTO card = null;
|
||||||
if (lock.isAccumulatePicks()) {
|
if (lock.getLatestOpeningtime() != null && lock.getLatestOpeningtime().isAfter(LocalDateTime.now())) {
|
||||||
|
card = getGreenCard();
|
||||||
|
} else if (lock.isAccumulatePicks()) {
|
||||||
if (lock.getNextCardIn().isAfter(LocalDateTime.now())) {
|
if (lock.getNextCardIn().isAfter(LocalDateTime.now())) {
|
||||||
lock.setOpenPicks(lock.getOpenPicks() == null ? 1 : lock.getOpenPicks() + 1);
|
lock.setOpenPicks(lock.getOpenPicks() == null ? 1 : lock.getOpenPicks() + 1);
|
||||||
}
|
}
|
||||||
@@ -51,7 +50,6 @@ public class CardLockService extends ProcessLock {
|
|||||||
if (lock.getNextCardIn().isBefore(LocalDateTime.now())) {
|
if (lock.getNextCardIn().isBefore(LocalDateTime.now())) {
|
||||||
lock.setNextCardIn(LocalDateTime.now().plusMinutes(lock.getPickEveryMinute()));
|
lock.setNextCardIn(LocalDateTime.now().plusMinutes(lock.getPickEveryMinute()));
|
||||||
card = getRandomCard();
|
card = getRandomCard();
|
||||||
lock.getAvailableCards().remove(card.card());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cardLockRepository.save(lock);
|
cardLockRepository.save(lock);
|
||||||
@@ -67,6 +65,10 @@ public class CardLockService extends ProcessLock {
|
|||||||
return card.get().processCard(this);
|
return card.get().processCard(this);
|
||||||
}
|
}
|
||||||
LOGGER.error("Keine Karten mehr im Lock - generiere Notfall Grüne Karte");
|
LOGGER.error("Keine Karten mehr im Lock - generiere Notfall Grüne Karte");
|
||||||
|
return getGreenCard();
|
||||||
|
}
|
||||||
|
|
||||||
|
private CardDTO getGreenCard() {
|
||||||
return new CardDTO(CardEnum.GREEN, lock.getUnlockCode());
|
return new CardDTO(CardEnum.GREEN, lock.getUnlockCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +93,8 @@ public class CardLockService extends ProcessLock {
|
|||||||
|
|
||||||
public void unlock(String unlockCode) {
|
public void unlock(String unlockCode) {
|
||||||
this.lock.setUnlockTime(LocalDateTime.now());
|
this.lock.setUnlockTime(LocalDateTime.now());
|
||||||
boolean valid = true;
|
// Self-Lock oder automatische Entsperrung ohne Keyholder-Zustimmung → ungültig
|
||||||
|
boolean valid = lock.getKeyholder() != null && !lock.isEmergencyAutoUnlocked();
|
||||||
if (!this.lock.isTestLock()) {
|
if (!this.lock.isTestLock()) {
|
||||||
if (Duration.between(lock.getStartTime(), lock.getUnlockTime()).toHours() > 24) {
|
if (Duration.between(lock.getStartTime(), lock.getUnlockTime()).toHours() > 24) {
|
||||||
Set<LocalDate> verifications = verificationRepository.findByLockId(this.lock.getLockId()).stream()
|
Set<LocalDate> verifications = verificationRepository.findByLockId(this.lock.getLockId()).stream()
|
||||||
@@ -159,6 +162,11 @@ public class CardLockService extends ProcessLock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String task() {
|
public String task() {
|
||||||
|
// Non-RANDOM modes are handled by the controller after the card is drawn
|
||||||
|
if (!"RANDOM".equals(lock.getTaskCardMode())) {
|
||||||
|
LOGGER.debug("Task card drawn in {} mode – skipping random assignment", lock.getTaskCardMode());
|
||||||
|
return "";
|
||||||
|
}
|
||||||
LOGGER.debug("Apply random task");
|
LOGGER.debug("Apply random task");
|
||||||
var tasks = lock.getTasks();
|
var tasks = lock.getTasks();
|
||||||
if (!tasks.isEmpty()) {
|
if (!tasks.isEmpty()) {
|
||||||
@@ -169,17 +177,19 @@ public class CardLockService extends ProcessLock {
|
|||||||
|
|
||||||
public String task(Task task) {
|
public String task(Task task) {
|
||||||
LOGGER.debug("Apply task {}", task);
|
LOGGER.debug("Apply task {}", task);
|
||||||
lock.setCurrentTask(task.getText());
|
lock.setCurrentTask(task.resolveTitle());
|
||||||
if (task.getMinutes() != null) {
|
lock.setCurrentTaskDescription(task.getDescription());
|
||||||
freeze(task.getMinutes());
|
if (task.getMinutes() != null && task.getMinutes() > 0) {
|
||||||
|
lock.setTaskFrozenUntil(LocalDateTime.now().plusMinutes(task.getMinutes()));
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public String clearTask() {
|
public String clearTask() {
|
||||||
LOGGER.debug("Clear task");
|
LOGGER.debug("Clear task");
|
||||||
lock.setFrozenUntill(null);
|
|
||||||
lock.setCurrentTask(null);
|
lock.setCurrentTask(null);
|
||||||
|
lock.setCurrentTaskDescription(null);
|
||||||
|
lock.setTaskFrozenUntil(null);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +32,8 @@ public class CardlockTemplateController {
|
|||||||
Integer hygineOpeningDurationMinutes,
|
Integer hygineOpeningDurationMinutes,
|
||||||
Integer hygineOpeningEveryMinites,
|
Integer hygineOpeningEveryMinites,
|
||||||
List<Task> tasks,
|
List<Task> tasks,
|
||||||
boolean requiresVerification
|
boolean requiresVerification,
|
||||||
|
String taskCardMode
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private Map<String, Object> toDto(CardlockTemplateEntity t) {
|
private Map<String, Object> toDto(CardlockTemplateEntity t) {
|
||||||
@@ -48,6 +49,7 @@ public class CardlockTemplateController {
|
|||||||
dto.put("hygineOpeningDurationMinutes", t.getHygineOpeningDurationMinutes());
|
dto.put("hygineOpeningDurationMinutes", t.getHygineOpeningDurationMinutes());
|
||||||
dto.put("tasks", t.getTasks() != null ? t.getTasks() : List.of());
|
dto.put("tasks", t.getTasks() != null ? t.getTasks() : List.of());
|
||||||
dto.put("requiresVerification", t.isRequiresVerification());
|
dto.put("requiresVerification", t.isRequiresVerification());
|
||||||
|
dto.put("taskCardMode", t.getTaskCardMode());
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,5 +129,6 @@ public class CardlockTemplateController {
|
|||||||
t.setHygineOpeningDurationMinutes(req.hygineOpeningDurationMinutes());
|
t.setHygineOpeningDurationMinutes(req.hygineOpeningDurationMinutes());
|
||||||
t.setTasks(req.tasks() != null ? req.tasks() : List.of());
|
t.setTasks(req.tasks() != null ? req.tasks() : List.of());
|
||||||
t.setRequiresVerification(req.requiresVerification());
|
t.setRequiresVerification(req.requiresVerification());
|
||||||
|
t.setTaskCardMode(req.taskCardMode() != null ? req.taskCardMode() : "RANDOM");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ public class CardlockTemplateEntity {
|
|||||||
@Column
|
@Column
|
||||||
private boolean requiresVerification;
|
private boolean requiresVerification;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String taskCardMode = "RANDOM";
|
||||||
|
|
||||||
public UUID getTemplateId() { return templateId; }
|
public UUID getTemplateId() { return templateId; }
|
||||||
public void setTemplateId(UUID templateId) { this.templateId = templateId; }
|
public void setTemplateId(UUID templateId) { this.templateId = templateId; }
|
||||||
|
|
||||||
@@ -88,4 +91,7 @@ public class CardlockTemplateEntity {
|
|||||||
|
|
||||||
public boolean isRequiresVerification() { return requiresVerification; }
|
public boolean isRequiresVerification() { return requiresVerification; }
|
||||||
public void setRequiresVerification(boolean requiresVerification) { this.requiresVerification = requiresVerification; }
|
public void setRequiresVerification(boolean requiresVerification) { this.requiresVerification = requiresVerification; }
|
||||||
|
|
||||||
|
public String getTaskCardMode() { return taskCardMode != null ? taskCardMode : "RANDOM"; }
|
||||||
|
public void setTaskCardMode(String taskCardMode) { this.taskCardMode = taskCardMode; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "community_task_vote")
|
||||||
|
public class CommunityTaskVoteEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.UUID)
|
||||||
|
private UUID voteSessionId;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private UUID lockId;
|
||||||
|
|
||||||
|
/** ACTIVE | COMPLETED */
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String status = "ACTIVE";
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private LocalDateTime expiresAt;
|
||||||
|
|
||||||
|
/** true = TestLock, nicht der Community zeigen */
|
||||||
|
@Column(nullable = false)
|
||||||
|
private boolean testLock = false;
|
||||||
|
|
||||||
|
/** null until completed */
|
||||||
|
@Column
|
||||||
|
private Integer winningTaskIndex;
|
||||||
|
|
||||||
|
public UUID getVoteSessionId() { return voteSessionId; }
|
||||||
|
public void setVoteSessionId(UUID id) { this.voteSessionId = id; }
|
||||||
|
|
||||||
|
public UUID getLockId() { return lockId; }
|
||||||
|
public void setLockId(UUID lockId) { this.lockId = lockId; }
|
||||||
|
|
||||||
|
public String getStatus() { return status; }
|
||||||
|
public void setStatus(String status) { this.status = status; }
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||||
|
public void setCreatedAt(LocalDateTime t) { this.createdAt = t; }
|
||||||
|
|
||||||
|
public LocalDateTime getExpiresAt() { return expiresAt; }
|
||||||
|
public void setExpiresAt(LocalDateTime t) { this.expiresAt = t; }
|
||||||
|
|
||||||
|
public boolean isTestLock() { return testLock; }
|
||||||
|
public void setTestLock(boolean testLock) { this.testLock = testLock; }
|
||||||
|
|
||||||
|
public Integer getWinningTaskIndex() { return winningTaskIndex; }
|
||||||
|
public void setWinningTaskIndex(Integer i) { this.winningTaskIndex = i; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "community_task_vote_entry",
|
||||||
|
uniqueConstraints = @UniqueConstraint(columnNames = {"voteSessionId", "voterUserId"}))
|
||||||
|
public class CommunityTaskVoteEntryEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.UUID)
|
||||||
|
private UUID entryId;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private UUID voteSessionId;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private UUID voterUserId;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private int taskIndex;
|
||||||
|
|
||||||
|
public UUID getEntryId() { return entryId; }
|
||||||
|
public void setEntryId(UUID entryId) { this.entryId = entryId; }
|
||||||
|
|
||||||
|
public UUID getVoteSessionId() { return voteSessionId; }
|
||||||
|
public void setVoteSessionId(UUID id) { this.voteSessionId = id; }
|
||||||
|
|
||||||
|
public UUID getVoterUserId() { return voterUserId; }
|
||||||
|
public void setVoterUserId(UUID id) { this.voterUserId = id; }
|
||||||
|
|
||||||
|
public int getTaskIndex() { return taskIndex; }
|
||||||
|
public void setTaskIndex(int taskIndex) { this.taskIndex = taskIndex; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface CommunityTaskVoteEntryRepository extends JpaRepository<CommunityTaskVoteEntryEntity, UUID> {
|
||||||
|
List<CommunityTaskVoteEntryEntity> findByVoteSessionId(UUID voteSessionId);
|
||||||
|
boolean existsByVoteSessionIdAndVoterUserId(UUID voteSessionId, UUID voterUserId);
|
||||||
|
Integer countByVoteSessionIdAndTaskIndex(UUID voteSessionId, int taskIndex);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface CommunityTaskVoteRepository extends JpaRepository<CommunityTaskVoteEntity, UUID> {
|
||||||
|
List<CommunityTaskVoteEntity> findByStatus(String status);
|
||||||
|
List<CommunityTaskVoteEntity> findByStatusAndExpiresAtBefore(String status, LocalDateTime time);
|
||||||
|
boolean existsByLockIdAndStatus(UUID lockId, String status);
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package de.oaa.xxx.games.chastity.cardlock;
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
import de.oaa.xxx.games.chastity.CardLockService;
|
|
||||||
|
|
||||||
public class DoubleUpCard implements Card {
|
public class DoubleUpCard implements Card {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package de.oaa.xxx.games.chastity.cardlock;
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
import de.oaa.xxx.games.chastity.CardLockService;
|
|
||||||
|
|
||||||
public class FreezeCard implements Card {
|
public class FreezeCard implements Card {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package de.oaa.xxx.games.chastity.cardlock;
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
import de.oaa.xxx.games.chastity.CardLockService;
|
|
||||||
|
|
||||||
public class GreenCard implements Card {
|
public class GreenCard implements Card {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "keyholder_task_choice")
|
||||||
|
public class KeyholderTaskChoiceEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.UUID)
|
||||||
|
private UUID choiceId;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private UUID lockId;
|
||||||
|
|
||||||
|
/** PENDING | CHOSEN */
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String status = "PENDING";
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
public UUID getChoiceId() { return choiceId; }
|
||||||
|
public void setChoiceId(UUID choiceId) { this.choiceId = choiceId; }
|
||||||
|
|
||||||
|
public UUID getLockId() { return lockId; }
|
||||||
|
public void setLockId(UUID lockId) { this.lockId = lockId; }
|
||||||
|
|
||||||
|
public String getStatus() { return status; }
|
||||||
|
public void setStatus(String status) { this.status = status; }
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||||
|
public void setCreatedAt(LocalDateTime t) { this.createdAt = t; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface KeyholderTaskChoiceRepository extends JpaRepository<KeyholderTaskChoiceEntity, UUID> {
|
||||||
|
List<KeyholderTaskChoiceEntity> findByLockIdAndStatus(UUID lockId, String status);
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package de.oaa.xxx.games.chastity.cardlock;
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
import de.oaa.xxx.games.chastity.CardLockService;
|
|
||||||
|
|
||||||
public class RedCard implements Card {
|
public class RedCard implements Card {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package de.oaa.xxx.games.chastity.cardlock;
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
import de.oaa.xxx.games.chastity.CardLockService;
|
|
||||||
|
|
||||||
public class ResetCard implements Card {
|
public class ResetCard implements Card {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package de.oaa.xxx.games.chastity.cardlock;
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
import de.oaa.xxx.games.chastity.CardLockService;
|
|
||||||
|
|
||||||
public class TaskCard implements Card {
|
public class TaskCard implements Card {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
|
import de.oaa.xxx.games.chastity.tasks.AssignedTaskEntity;
|
||||||
|
import de.oaa.xxx.games.chastity.tasks.AssignedTaskRepository;
|
||||||
|
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||||
|
import de.oaa.xxx.social.SseService;
|
||||||
|
import de.oaa.xxx.user.UserRepository;
|
||||||
|
import de.oaa.xxx.social.entity.MessageEntity;
|
||||||
|
import de.oaa.xxx.social.repository.MessageRepository;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/task-card")
|
||||||
|
public class TaskCardController {
|
||||||
|
|
||||||
|
private final CardlockRepository cardlockRepository;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final KeyholderTaskChoiceRepository keyholderTaskChoiceRepository;
|
||||||
|
private final CommunityTaskVoteRepository communityTaskVoteRepository;
|
||||||
|
private final CommunityTaskVoteEntryRepository communityTaskVoteEntryRepository;
|
||||||
|
private final AssignedTaskRepository assignedTaskRepository;
|
||||||
|
private final MessageRepository messageRepository;
|
||||||
|
private final SseService sseService;
|
||||||
|
|
||||||
|
public TaskCardController(CardlockRepository cardlockRepository,
|
||||||
|
UserRepository userRepository,
|
||||||
|
KeyholderTaskChoiceRepository keyholderTaskChoiceRepository,
|
||||||
|
CommunityTaskVoteRepository communityTaskVoteRepository,
|
||||||
|
CommunityTaskVoteEntryRepository communityTaskVoteEntryRepository,
|
||||||
|
AssignedTaskRepository assignedTaskRepository,
|
||||||
|
MessageRepository messageRepository,
|
||||||
|
SseService sseService) {
|
||||||
|
this.cardlockRepository = cardlockRepository;
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.keyholderTaskChoiceRepository = keyholderTaskChoiceRepository;
|
||||||
|
this.communityTaskVoteRepository = communityTaskVoteRepository;
|
||||||
|
this.communityTaskVoteEntryRepository = communityTaskVoteEntryRepository;
|
||||||
|
this.assignedTaskRepository = assignedTaskRepository;
|
||||||
|
this.messageRepository = messageRepository;
|
||||||
|
this.sseService = sseService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Keyholder: ausstehende Aufgaben-Karten-Entscheidungen ─────────────────
|
||||||
|
|
||||||
|
@GetMapping("/keyholder/choices")
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public ResponseEntity<List<Map<String, Object>>> getPendingChoices(Principal principal) {
|
||||||
|
var meOpt = userRepository.findByEmail(principal.getName());
|
||||||
|
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||||
|
UUID myId = meOpt.get().getUserId();
|
||||||
|
|
||||||
|
// Alle Locks bei denen ich Keyholder bin
|
||||||
|
var locks = cardlockRepository.findByKeyholderAndUnlockTimeIsNull(myId);
|
||||||
|
List<Map<String, Object>> result = new ArrayList<>();
|
||||||
|
|
||||||
|
for (var lock : locks) {
|
||||||
|
var pending = keyholderTaskChoiceRepository.findByLockIdAndStatus(lock.getLockId(), "PENDING");
|
||||||
|
if (pending.isEmpty()) continue;
|
||||||
|
|
||||||
|
var lockee = userRepository.findById(lock.getLockee()).orElse(null);
|
||||||
|
List<Map<String, Object>> taskList = buildTaskList(lock.getTasks());
|
||||||
|
|
||||||
|
for (var choice : pending) {
|
||||||
|
Map<String, Object> m = new LinkedHashMap<>();
|
||||||
|
m.put("choiceId", choice.getChoiceId().toString());
|
||||||
|
m.put("lockId", lock.getLockId().toString());
|
||||||
|
m.put("lockName", lock.getName() != null ? lock.getName() : "");
|
||||||
|
m.put("lockeeName", lockee != null ? lockee.getName() : "");
|
||||||
|
m.put("createdAt", choice.getCreatedAt().toString());
|
||||||
|
m.put("tasks", taskList);
|
||||||
|
result.add(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
record PenaltyRequest(Integer penaltyFreezeMinutes, Integer penaltyRedCards) {}
|
||||||
|
|
||||||
|
@PostMapping("/keyholder/choices/{choiceId}/choose/{taskIndex}")
|
||||||
|
@Transactional
|
||||||
|
public ResponseEntity<Void> chooseTask(@PathVariable UUID choiceId,
|
||||||
|
@PathVariable int taskIndex,
|
||||||
|
@org.springframework.web.bind.annotation.RequestBody(required = false) PenaltyRequest penalty,
|
||||||
|
Principal principal) {
|
||||||
|
var meOpt = userRepository.findByEmail(principal.getName());
|
||||||
|
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||||
|
UUID myId = meOpt.get().getUserId();
|
||||||
|
|
||||||
|
var choiceOpt = keyholderTaskChoiceRepository.findById(choiceId);
|
||||||
|
if (choiceOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||||
|
var choice = choiceOpt.get();
|
||||||
|
|
||||||
|
var lockOpt = cardlockRepository.findById(choice.getLockId());
|
||||||
|
if (lockOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||||
|
var lock = lockOpt.get();
|
||||||
|
|
||||||
|
if (!myId.equals(lock.getKeyholder())) return ResponseEntity.status(403).build();
|
||||||
|
if (!"PENDING".equals(choice.getStatus())) return ResponseEntity.status(409).build();
|
||||||
|
|
||||||
|
List<Task> tasks = lock.getTasks();
|
||||||
|
if (tasks == null || taskIndex < 0 || taskIndex >= tasks.size())
|
||||||
|
return ResponseEntity.badRequest().build();
|
||||||
|
|
||||||
|
Task task = tasks.get(taskIndex);
|
||||||
|
AssignedTaskEntity assigned = new AssignedTaskEntity();
|
||||||
|
assigned.setLockId(lock.getLockId());
|
||||||
|
assigned.setTaskTitle(task.resolveTitle());
|
||||||
|
assigned.setTaskDescription(task.getDescription());
|
||||||
|
assigned.setTaskText(task.resolveTitle());
|
||||||
|
assigned.setTaskMinutes(task.getMinutes());
|
||||||
|
assigned.setAssignedAt(LocalDateTime.now());
|
||||||
|
assigned.setAcceptDeadline(LocalDateTime.now().plusHours(1));
|
||||||
|
assigned.setStatus("PENDING");
|
||||||
|
if (penalty != null) {
|
||||||
|
assigned.setPenaltyFreezeMinutes(penalty.penaltyFreezeMinutes());
|
||||||
|
assigned.setPenaltyRedCards(penalty.penaltyRedCards());
|
||||||
|
}
|
||||||
|
assignedTaskRepository.save(assigned);
|
||||||
|
|
||||||
|
choice.setStatus("CHOSEN");
|
||||||
|
keyholderTaskChoiceRepository.save(choice);
|
||||||
|
|
||||||
|
sendMessage(myId, lock.getLockee(),
|
||||||
|
"Dein Keyholder hat eine Aufgabe für dich ausgewählt.",
|
||||||
|
"/activelock.html?lockId=" + lock.getLockId());
|
||||||
|
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Community: aktive Abstimmungen ────────────────────────────────────────
|
||||||
|
|
||||||
|
@GetMapping("/community/votes")
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public ResponseEntity<List<Map<String, Object>>> getActiveVotes(Principal principal) {
|
||||||
|
var meOpt = userRepository.findByEmail(principal.getName());
|
||||||
|
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||||
|
UUID myId = meOpt.get().getUserId();
|
||||||
|
|
||||||
|
var activeVotes = communityTaskVoteRepository.findByStatus("ACTIVE");
|
||||||
|
List<Map<String, Object>> result = new ArrayList<>();
|
||||||
|
|
||||||
|
for (var vote : activeVotes) {
|
||||||
|
if (vote.isTestLock()) continue;
|
||||||
|
var lockOpt = cardlockRepository.findById(vote.getLockId());
|
||||||
|
if (lockOpt.isEmpty()) continue;
|
||||||
|
var lock = lockOpt.get();
|
||||||
|
|
||||||
|
var lockee = userRepository.findById(lock.getLockee()).orElse(null);
|
||||||
|
List<Task> tasks = lock.getTasks();
|
||||||
|
if (tasks == null || tasks.isEmpty()) continue;
|
||||||
|
|
||||||
|
List<Map<String, Object>> taskList = buildTaskList(tasks);
|
||||||
|
|
||||||
|
// Stimmenanzahl pro Task
|
||||||
|
List<Integer> voteCounts = new ArrayList<>();
|
||||||
|
for (int i = 0; i < tasks.size(); i++) {
|
||||||
|
voteCounts.add(communityTaskVoteEntryRepository
|
||||||
|
.countByVoteSessionIdAndTaskIndex(vote.getVoteSessionId(), i));
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer myVote = communityTaskVoteEntryRepository.findByVoteSessionId(vote.getVoteSessionId())
|
||||||
|
.stream().filter(e -> myId.equals(e.getVoterUserId()))
|
||||||
|
.map(CommunityTaskVoteEntryEntity::getTaskIndex).findFirst().orElse(null);
|
||||||
|
|
||||||
|
Map<String, Object> m = new LinkedHashMap<>();
|
||||||
|
m.put("voteSessionId", vote.getVoteSessionId().toString());
|
||||||
|
m.put("lockId", lock.getLockId().toString());
|
||||||
|
m.put("lockeeName", lockee != null ? lockee.getName() : "");
|
||||||
|
m.put("expiresAt", vote.getExpiresAt().toString());
|
||||||
|
m.put("tasks", taskList);
|
||||||
|
m.put("voteCounts", voteCounts);
|
||||||
|
m.put("myVote", myVote);
|
||||||
|
result.add(m);
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/community/votes/{voteSessionId}/vote/{taskIndex}")
|
||||||
|
@Transactional
|
||||||
|
public ResponseEntity<Void> castVote(@PathVariable UUID voteSessionId,
|
||||||
|
@PathVariable int taskIndex,
|
||||||
|
Principal principal) {
|
||||||
|
var meOpt = userRepository.findByEmail(principal.getName());
|
||||||
|
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||||
|
UUID myId = meOpt.get().getUserId();
|
||||||
|
|
||||||
|
var voteOpt = communityTaskVoteRepository.findById(voteSessionId);
|
||||||
|
if (voteOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||||
|
var vote = voteOpt.get();
|
||||||
|
|
||||||
|
if (!"ACTIVE".equals(vote.getStatus()) || vote.getExpiresAt().isBefore(LocalDateTime.now()))
|
||||||
|
return ResponseEntity.status(409).build();
|
||||||
|
|
||||||
|
var lockOpt = cardlockRepository.findById(vote.getLockId());
|
||||||
|
if (lockOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||||
|
var lock = lockOpt.get();
|
||||||
|
|
||||||
|
if (lock.getTasks() == null || taskIndex < 0 || taskIndex >= lock.getTasks().size())
|
||||||
|
return ResponseEntity.badRequest().build();
|
||||||
|
|
||||||
|
if (communityTaskVoteEntryRepository.existsByVoteSessionIdAndVoterUserId(voteSessionId, myId))
|
||||||
|
return ResponseEntity.status(409).build();
|
||||||
|
|
||||||
|
CommunityTaskVoteEntryEntity entry = new CommunityTaskVoteEntryEntity();
|
||||||
|
entry.setVoteSessionId(voteSessionId);
|
||||||
|
entry.setVoterUserId(myId);
|
||||||
|
entry.setTaskIndex(taskIndex);
|
||||||
|
communityTaskVoteEntryRepository.save(entry);
|
||||||
|
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private List<Map<String, Object>> buildTaskList(List<Task> tasks) {
|
||||||
|
if (tasks == null) return List.of();
|
||||||
|
List<Map<String, Object>> list = new ArrayList<>();
|
||||||
|
for (int i = 0; i < tasks.size(); i++) {
|
||||||
|
Task t = tasks.get(i);
|
||||||
|
Map<String, Object> m = new LinkedHashMap<>();
|
||||||
|
m.put("index", i);
|
||||||
|
m.put("title", t.resolveTitle());
|
||||||
|
m.put("description", t.getDescription() != null ? t.getDescription() : "");
|
||||||
|
m.put("minutes", t.getMinutes() != null ? t.getMinutes() : 0);
|
||||||
|
list.add(m);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessage(UUID fromId, UUID toId, String text, String targetUrl) {
|
||||||
|
if (toId == null) return;
|
||||||
|
MessageEntity msg = new MessageEntity();
|
||||||
|
msg.setMessageId(java.util.UUID.randomUUID());
|
||||||
|
msg.setSenderId(fromId);
|
||||||
|
msg.setReceiverId(toId);
|
||||||
|
msg.setText(text);
|
||||||
|
msg.setSystemMessage(true);
|
||||||
|
msg.setTargetUrl(targetUrl);
|
||||||
|
msg.setSentAt(java.time.LocalDateTime.now());
|
||||||
|
messageRepository.save(msg);
|
||||||
|
sseService.push(toId, "notification", Map.of("text", text));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import de.oaa.xxx.games.chastity.tasks.AssignedTaskEntity;
|
||||||
|
import de.oaa.xxx.games.chastity.tasks.AssignedTaskRepository;
|
||||||
|
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||||
|
import de.oaa.xxx.social.SseService;
|
||||||
|
import de.oaa.xxx.social.entity.MessageEntity;
|
||||||
|
import de.oaa.xxx.social.repository.MessageRepository;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class TaskVoteScheduler {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(TaskVoteScheduler.class);
|
||||||
|
|
||||||
|
private final CommunityTaskVoteRepository communityTaskVoteRepository;
|
||||||
|
private final CommunityTaskVoteEntryRepository communityTaskVoteEntryRepository;
|
||||||
|
private final CardlockRepository cardlockRepository;
|
||||||
|
private final AssignedTaskRepository assignedTaskRepository;
|
||||||
|
private final MessageRepository messageRepository;
|
||||||
|
private final SseService sseService;
|
||||||
|
|
||||||
|
public TaskVoteScheduler(CommunityTaskVoteRepository communityTaskVoteRepository,
|
||||||
|
CommunityTaskVoteEntryRepository communityTaskVoteEntryRepository,
|
||||||
|
CardlockRepository cardlockRepository,
|
||||||
|
AssignedTaskRepository assignedTaskRepository,
|
||||||
|
MessageRepository messageRepository,
|
||||||
|
SseService sseService) {
|
||||||
|
this.communityTaskVoteRepository = communityTaskVoteRepository;
|
||||||
|
this.communityTaskVoteEntryRepository = communityTaskVoteEntryRepository;
|
||||||
|
this.cardlockRepository = cardlockRepository;
|
||||||
|
this.assignedTaskRepository = assignedTaskRepository;
|
||||||
|
this.messageRepository = messageRepository;
|
||||||
|
this.sseService = sseService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Scheduled(fixedDelay = 60_000)
|
||||||
|
@Transactional
|
||||||
|
public void processExpiredVotes() {
|
||||||
|
var expired = communityTaskVoteRepository
|
||||||
|
.findByStatusAndExpiresAtBefore("ACTIVE", LocalDateTime.now());
|
||||||
|
|
||||||
|
for (var vote : expired) {
|
||||||
|
LOG.debug("Processing expired community task vote {}", vote.getVoteSessionId());
|
||||||
|
|
||||||
|
var lockOpt = cardlockRepository.findById(vote.getLockId());
|
||||||
|
if (lockOpt.isEmpty()) {
|
||||||
|
vote.setStatus("COMPLETED");
|
||||||
|
communityTaskVoteRepository.save(vote);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var lock = lockOpt.get();
|
||||||
|
List<Task> tasks = lock.getTasks();
|
||||||
|
if (tasks == null || tasks.isEmpty()) {
|
||||||
|
vote.setStatus("COMPLETED");
|
||||||
|
communityTaskVoteRepository.save(vote);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stimmen auszählen
|
||||||
|
var entries = communityTaskVoteEntryRepository.findByVoteSessionId(vote.getVoteSessionId());
|
||||||
|
int winnerIndex;
|
||||||
|
if (entries.isEmpty()) {
|
||||||
|
winnerIndex = new Random().nextInt(tasks.size());
|
||||||
|
LOG.debug("No votes → random task index {}", winnerIndex);
|
||||||
|
} else {
|
||||||
|
int[] counts = new int[tasks.size()];
|
||||||
|
for (var e : entries) {
|
||||||
|
if (e.getTaskIndex() >= 0 && e.getTaskIndex() < tasks.size()) {
|
||||||
|
counts[e.getTaskIndex()]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int max = Arrays.stream(counts).max().getAsInt();
|
||||||
|
// Alle Tasks mit Maximalstimmen sammeln → zufällige Auswahl bei Gleichstand
|
||||||
|
List<Integer> winners = new ArrayList<>();
|
||||||
|
for (int i = 0; i < counts.length; i++) {
|
||||||
|
if (counts[i] == max) winners.add(i);
|
||||||
|
}
|
||||||
|
winnerIndex = winners.get(new Random().nextInt(winners.size()));
|
||||||
|
LOG.debug("Vote winner: task index {} with {} votes", winnerIndex, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
Task task = tasks.get(winnerIndex);
|
||||||
|
AssignedTaskEntity assigned = new AssignedTaskEntity();
|
||||||
|
assigned.setLockId(lock.getLockId());
|
||||||
|
assigned.setTaskTitle(task.resolveTitle());
|
||||||
|
assigned.setTaskDescription(task.getDescription());
|
||||||
|
assigned.setTaskText(task.resolveTitle());
|
||||||
|
assigned.setTaskMinutes(task.getMinutes());
|
||||||
|
assigned.setAssignedAt(LocalDateTime.now());
|
||||||
|
assigned.setAcceptDeadline(LocalDateTime.now().plusHours(1));
|
||||||
|
assigned.setStatus("PENDING");
|
||||||
|
assignedTaskRepository.save(assigned);
|
||||||
|
|
||||||
|
vote.setStatus("COMPLETED");
|
||||||
|
vote.setWinningTaskIndex(winnerIndex);
|
||||||
|
communityTaskVoteRepository.save(vote);
|
||||||
|
|
||||||
|
// Lockee benachrichtigen
|
||||||
|
sendMessage(lock.getLockee(),
|
||||||
|
"Die Community hat für deine Aufgabe abgestimmt: \"" + task.resolveTitle() + "\"",
|
||||||
|
"/activelock.html?lockId=" + lock.getLockId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessage(UUID toId, String text, String targetUrl) {
|
||||||
|
if (toId == null) return;
|
||||||
|
MessageEntity msg = new MessageEntity();
|
||||||
|
msg.setMessageId(UUID.randomUUID());
|
||||||
|
msg.setSenderId(toId); // System-Nachricht, kein echter Sender
|
||||||
|
msg.setReceiverId(toId);
|
||||||
|
msg.setText(text);
|
||||||
|
msg.setSystemMessage(true);
|
||||||
|
msg.setTargetUrl(targetUrl);
|
||||||
|
msg.setSentAt(LocalDateTime.now());
|
||||||
|
messageRepository.save(msg);
|
||||||
|
sseService.push(toId, "notification", Map.of("text", text));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "unlock_code_history")
|
||||||
|
public class UnlockCodeHistoryEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.UUID)
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private UUID userId;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private UUID lockId;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String lockName;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String unlockCode;
|
||||||
|
|
||||||
|
/** GREEN_CARD | HYGIENE_OPEN | HYGIENE_CLOSE | KEYHOLDER_UNLOCK */
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String source;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private LocalDateTime receivedAt;
|
||||||
|
|
||||||
|
public UUID getId() { return id; }
|
||||||
|
public UUID getUserId() { return userId; }
|
||||||
|
public void setUserId(UUID userId) { this.userId = userId; }
|
||||||
|
public UUID getLockId() { return lockId; }
|
||||||
|
public void setLockId(UUID lockId) { this.lockId = lockId; }
|
||||||
|
public String getLockName() { return lockName; }
|
||||||
|
public void setLockName(String lockName) { this.lockName = lockName; }
|
||||||
|
public String getUnlockCode() { return unlockCode; }
|
||||||
|
public void setUnlockCode(String unlockCode) { this.unlockCode = unlockCode; }
|
||||||
|
public String getSource() { return source; }
|
||||||
|
public void setSource(String source) { this.source = source; }
|
||||||
|
public LocalDateTime getReceivedAt() { return receivedAt; }
|
||||||
|
public void setReceivedAt(LocalDateTime receivedAt) { this.receivedAt = receivedAt; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package de.oaa.xxx.games.chastity.cardlock;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface UnlockCodeHistoryRepository extends JpaRepository<UnlockCodeHistoryEntity, UUID> {
|
||||||
|
|
||||||
|
List<UnlockCodeHistoryEntity> findByUserIdOrderByReceivedAtDesc(UUID userId, Pageable pageable);
|
||||||
|
|
||||||
|
/** Prüft, ob für dieses Lock und diese Quelle bereits der gleiche Code gespeichert ist. */
|
||||||
|
boolean existsByLockIdAndSourceAndUnlockCode(UUID lockId, String source, String unlockCode);
|
||||||
|
|
||||||
|
/** Alle Einträge des Users aufsteigend (für Cleanup: älteste löschen). */
|
||||||
|
@Query("SELECT e FROM UnlockCodeHistoryEntity e WHERE e.userId = :userId ORDER BY e.receivedAt ASC")
|
||||||
|
List<UnlockCodeHistoryEntity> findByUserIdOrderByReceivedAtAsc(@Param("userId") UUID userId);
|
||||||
|
|
||||||
|
long countByUserId(UUID userId);
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user