großes refactoring
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
#Sat Mar 21 08:14:24 CET 2026
|
||||
#Sun Mar 22 19:48:43 CET 2026
|
||||
display=\:0
|
||||
host=Mario-Linux
|
||||
process-id=23243
|
||||
process-id=106706
|
||||
user=mario
|
||||
|
||||
2926
.metadata/.log
2926
.metadata/.log
File diff suppressed because it is too large
Load Diff
@@ -1,24 +1,7 @@
|
||||
[ {
|
||||
"version" : "9.6.0-20260321035213+0000",
|
||||
"buildTime" : "20260321035213+0000",
|
||||
"commitId" : "cc9b7c946cf24a57b2119d39f1a33cf5493ea930",
|
||||
"current" : false,
|
||||
"snapshot" : true,
|
||||
"nightly" : true,
|
||||
"releaseNightly" : false,
|
||||
"activeRc" : false,
|
||||
"rcFor" : "",
|
||||
"milestoneFor" : "",
|
||||
"broken" : false,
|
||||
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260321035213+0000-bin.zip",
|
||||
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260321035213+0000-bin.zip.sha256",
|
||||
"checksum" : "e533696ad1e80c2878ed39c18b5252ac7e0bad6394ead2b93663656cd6591059",
|
||||
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260321035213+0000-wrapper.jar.sha256",
|
||||
"wrapperChecksum" : "f307680272dffdb8e636f1169adfbf693513005c80aa06e8d381f20390a06e6a"
|
||||
}, {
|
||||
"version" : "9.5.0-20260321014114+0000",
|
||||
"buildTime" : "20260321014114+0000",
|
||||
"commitId" : "e7d13113f033cc35f5b8c7e0eeb88906259a1410",
|
||||
"version" : "9.5.0-20260322013634+0000",
|
||||
"buildTime" : "20260322013634+0000",
|
||||
"commitId" : "01db0eb99f616dd415a084ffcce4cb2c185d5a2a",
|
||||
"current" : false,
|
||||
"snapshot" : true,
|
||||
"nightly" : false,
|
||||
@@ -27,10 +10,27 @@
|
||||
"rcFor" : "",
|
||||
"milestoneFor" : "",
|
||||
"broken" : false,
|
||||
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260321014114+0000-bin.zip",
|
||||
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260321014114+0000-bin.zip.sha256",
|
||||
"checksum" : "eeb50f4468d73f74a68fe62a16d371ccc7c54088d7d2f672adb12c4bce4104d5",
|
||||
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260321014114+0000-wrapper.jar.sha256",
|
||||
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260322013634+0000-bin.zip",
|
||||
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260322013634+0000-bin.zip.sha256",
|
||||
"checksum" : "3e8a6689594399f81087ad962b1c489e0ae57201af0c6c00ea63d9d07e48506e",
|
||||
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260322013634+0000-wrapper.jar.sha256",
|
||||
"wrapperChecksum" : "f307680272dffdb8e636f1169adfbf693513005c80aa06e8d381f20390a06e6a"
|
||||
}, {
|
||||
"version" : "9.6.0-20260322000231+0000",
|
||||
"buildTime" : "20260322000231+0000",
|
||||
"commitId" : "d63be1b9cd4d937f4a9f5cf7ee78eec20fe5354e",
|
||||
"current" : false,
|
||||
"snapshot" : true,
|
||||
"nightly" : true,
|
||||
"releaseNightly" : false,
|
||||
"activeRc" : false,
|
||||
"rcFor" : "",
|
||||
"milestoneFor" : "",
|
||||
"broken" : false,
|
||||
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260322000231+0000-bin.zip",
|
||||
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260322000231+0000-bin.zip.sha256",
|
||||
"checksum" : "80f3af587bc824675e2a5617c7f30a3b8e4888746d486bc8b3517ebf84f028a9",
|
||||
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260322000231+0000-wrapper.jar.sha256",
|
||||
"wrapperChecksum" : "f307680272dffdb8e636f1169adfbf693513005c80aa06e8d381f20390a06e6a"
|
||||
}, {
|
||||
"version" : "9.4.1",
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -11,6 +11,7 @@ INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.ec
|
||||
134995224.index
|
||||
4025319337.index
|
||||
900586112.index
|
||||
9341915.index
|
||||
2929476459.index
|
||||
2065500052.index
|
||||
3051047092.index
|
||||
|
||||
@@ -4,4 +4,13 @@
|
||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.history{GameHistoryEntity.java[GameHistoryEntity" modifiers="1" timestamp="1773860770365"/>
|
||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.bdsm.controller{BdsmGameController.java[BdsmGameController" modifiers="1" timestamp="1774017499554"/>
|
||||
<typeInfo handle="=xxxthegame/\/usr\/lib\/jvm\/java-21-openjdk-amd64\/lib\/jrt-fs.jar`java.base=/javadoc_location=/https:\/\/docs.oracle.com\/en\/java\/javase\/21\/docs\/api\/=/<java.util(UUID.class[UUID" modifiers="49" timestamp="1769125611000"/>
|
||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.cardlock{CardLockController.java[CardLockController" modifiers="1" timestamp="1774198561526"/>
|
||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.cardlock{CardLockEntity.java[CardLockEntity" modifiers="1" timestamp="1774171624571"/>
|
||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.timelock{TimeLockEntity.java[TimeLockEntity" modifiers="1" timestamp="1774174304909"/>
|
||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.timelock{TimeLockTemplateEntity.java[TimeLockTemplateEntity" modifiers="1" timestamp="1774174437363"/>
|
||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.tasks{Task.java[Task" modifiers="1" timestamp="1774181757140"/>
|
||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.keyholder{KeyholderVerificationRepository.java[KeyholderVerificationRepository" modifiers="513" timestamp="1774197695163"/>
|
||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.keyholder{KeyholderVerificationEntity.java[KeyholderVerificationEntity" modifiers="1" timestamp="1774198252535"/>
|
||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.community{CommunityVerificationEntity.java[CommunityVerificationEntity" modifiers="1" timestamp="1774198326209"/>
|
||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.games.chastity.common{Verification.java[Verification" modifiers="513" timestamp="1774198235758"/>
|
||||
</typeInfoHistroy>
|
||||
|
||||
@@ -1,28 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<qualifiedTypeNameHistroy>
|
||||
<fullyQualifiedTypeName name="java.util.List"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.verification.VerificationEntity"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.CodeCreator"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.verification.VerificationRepository"/>
|
||||
<fullyQualifiedTypeName name="org.hibernate.grammars.hql.HqlParser.LocalDateTimeContext"/>
|
||||
<fullyQualifiedTypeName name="java.util.stream.Collectors"/>
|
||||
<fullyQualifiedTypeName name="java.time.LocalDate"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.cardlock.CardDTO"/>
|
||||
<fullyQualifiedTypeName name="java.lang.Enum"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.cardlock.GreenCard"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.lockcontroll.LockControl"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.lockcontroll.TTLockControl"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.lockcontroll.TrustLockControl"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.lockcontroll.UnlockcodeLockControl"/>
|
||||
<fullyQualifiedTypeName name="java.util.Random"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.CardLockService"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.history.LockHistoryRepository"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.cardlock.Test"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.KeyholderCardLock"/>
|
||||
<fullyQualifiedTypeName name="lombok.Getter"/>
|
||||
<fullyQualifiedTypeName name="lombok.Setter"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.timelock.TimeLockService"/>
|
||||
<fullyQualifiedTypeName name="jakarta.persistence.Column"/>
|
||||
<fullyQualifiedTypeName name="java.time.temporal.ChronoUnit"/>
|
||||
<fullyQualifiedTypeName name="java.time.LocalDateTime"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.AbstractLockService"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.cardlock.TempOpeningReason"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationRepository"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.cardlock.UnlockCodeHistoryService"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.timelock.TimeLockEntity"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.timelock.TimeLockRepository"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.verification.VerificationVoteRepository"/>
|
||||
<fullyQualifiedTypeName name="jakarta.persistence.Entity"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.pillory.PilloryEntity"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.pillory.PilloryReason"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.pillory.PilloryRepository"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.timelock.TimeLockService"/>
|
||||
<fullyQualifiedTypeName name="jakarta.persistence.Table"/>
|
||||
<fullyQualifiedTypeName name="jakarta.persistence.Inheritance"/>
|
||||
<fullyQualifiedTypeName name="jakarta.persistence.DiscriminatorColumn"/>
|
||||
<fullyQualifiedTypeName name="jakarta.persistence.DiscriminatorType"/>
|
||||
<fullyQualifiedTypeName name="jakarta.persistence.EnumType"/>
|
||||
<fullyQualifiedTypeName name="org.springframework.stereotype.Controller"/>
|
||||
<fullyQualifiedTypeName name="java.util.UUID"/>
|
||||
<fullyQualifiedTypeName name="com.fasterxml.jackson.core.sym.Name3"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.tasks.TaskMode"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.common.BaseLockTemplateEntity"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.community.BaseCommunityDisplayDTO"/>
|
||||
<fullyQualifiedTypeName name="org.springframework.data.domain.Page"/>
|
||||
<fullyQualifiedTypeName name="org.springframework.data.domain.Sort.Direction"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.community.CommunityPilloryReason"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.community.BaseCommunityDisplayEntity"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.community.CommunityPilloryDTO"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.community.CommunityPilloryRepository"/>
|
||||
<fullyQualifiedTypeName name="org.springframework.web.bind.annotation.RestController"/>
|
||||
<fullyQualifiedTypeName name="org.springframework.web.bind.annotation.RequestMapping"/>
|
||||
<fullyQualifiedTypeName name="org.springframework.web.bind.annotation.GetMapping"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.user.UserRepository"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.community.CommunityTaskVoteRepository"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.community.CommunityTaskVoteEntryRepository"/>
|
||||
<fullyQualifiedTypeName name="org.springframework.web.bind.annotation.PathVariable"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.community.CommunityTaskVoteDTO"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.community.CommunityTaskVoteEntryDTO"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.common.BaseLockTemplateRepository"/>
|
||||
<fullyQualifiedTypeName name="org.springframework.data.jpa.repository.JpaRepository"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.common.BaseLockEntity"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.common.BaseLockRepository"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.tasks.Task"/>
|
||||
<fullyQualifiedTypeName name="java.util.ArrayList"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.community.CommunityTaskVoteDisplayEntryDTO"/>
|
||||
<fullyQualifiedTypeName name="org.springframework.http.ResponseEntity"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.community.CommunityTaskVoteDisplayDTO"/>
|
||||
<fullyQualifiedTypeName name="jakarta.persistence.Column"/>
|
||||
<fullyQualifiedTypeName name="java.util.List"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.common.Verification"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.keyholder.KeyholderVerificationEntity"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.common.VerificationDTO"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.common.CodeCreator"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.common.VerificationCommonDTO"/>
|
||||
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.community.CommunityVerificationEntity"/>
|
||||
<fullyQualifiedTypeName name="java.time.LocalDate"/>
|
||||
<fullyQualifiedTypeName name="java.time.LocalDateTime"/>
|
||||
</qualifiedTypeNameHistroy>
|
||||
|
||||
@@ -90,4 +90,21 @@
|
||||
<item key="DIALOG_HEIGHT" value="577"/>
|
||||
<item key="DIALOG_FONT_NAME" value="1|Ubuntu Sans|11.0|0|GTK|1|"/>
|
||||
</section>
|
||||
<section name="NewInterfaceCreationWizard.dialogBounds">
|
||||
<item key="DIALOG_X_ORIGIN" value="948"/>
|
||||
<item key="DIALOG_Y_ORIGIN" value="274"/>
|
||||
<item key="DIALOG_WIDTH" value="665"/>
|
||||
<item key="DIALOG_HEIGHT" value="605"/>
|
||||
<item key="DIALOG_FONT_NAME" value="1|Ubuntu Sans|11.0|0|GTK|1|"/>
|
||||
</section>
|
||||
<section name="NewRecordCreationWizard.dialogBounds">
|
||||
<item key="DIALOG_X_ORIGIN" value="965"/>
|
||||
<item key="DIALOG_Y_ORIGIN" value="247"/>
|
||||
<item key="DIALOG_WIDTH" value="631"/>
|
||||
<item key="DIALOG_HEIGHT" value="646"/>
|
||||
<item key="DIALOG_FONT_NAME" value="1|Ubuntu Sans|11.0|0|GTK|1|"/>
|
||||
</section>
|
||||
<section name="NewRecordWizardPage">
|
||||
<item key="create_unimplemented" value="false"/>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -35,3 +35,8 @@
|
||||
2026-03-21 08:03:15,921 [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-21 08:10:51,183 [Worker-1: 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-21 08:14:27,502 [Worker-8: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
||||
2026-03-22 08:51:09,620 [Worker-8: 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-22 08:53:24,304 [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-22 08:53:52,098 [Worker-1: 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-22 08:56:13,330 [Worker-5: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
||||
2026-03-22 19:48:46,352 [Worker-8: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#Sat Mar 21 08:14:24 CET 2026
|
||||
#Sun Mar 22 19:48:43 CET 2026
|
||||
org.eclipse.core.runtime=2
|
||||
org.eclipse.platform=4.39.0.v20260226-0420
|
||||
|
||||
@@ -136,7 +136,7 @@ public class BdsmGameService {
|
||||
newLock.setTasks(template.getTasks());
|
||||
newLock.setRequiresVerification(template.isRequiresVerification());
|
||||
newLock.setTestLock(false);
|
||||
newLock.setTaskCardMode(template.getTaskCardMode());
|
||||
newLock.setTaskMode(template.getTaskMode());
|
||||
|
||||
int codeLines = template.getUnlockCodeLength() != null ? template.getUnlockCodeLength() : 5;
|
||||
newLock.setUnlockCodeLength(codeLines);
|
||||
|
||||
@@ -37,25 +37,24 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import de.oaa.xxx.games.chastity.common.CodeCreator;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityTaskVoteRepository;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationEntity;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationVoteEntity;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationVoteRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderInvitationEntity;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderInvitationRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderTaskChoiceEntity;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderTaskChoiceRepository;
|
||||
import de.oaa.xxx.games.chastity.lockee.LockeeInvitationEntity;
|
||||
import de.oaa.xxx.games.chastity.lockee.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.tasks.TaskMode;
|
||||
import de.oaa.xxx.games.chastity.unlock.TempOpeningReason;
|
||||
import de.oaa.xxx.games.chastity.unlock.UnlockCodeHistoryRepository;
|
||||
import de.oaa.xxx.games.chastity.unlock.UnlockCodeHistoryService;
|
||||
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.games.chastity.vote.CommunityTaskVoteEntity;
|
||||
import de.oaa.xxx.games.chastity.vote.CommunityTaskVoteRepository;
|
||||
import de.oaa.xxx.social.SystemMessageService;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
|
||||
@@ -66,8 +65,8 @@ public class CardLockController {
|
||||
private final CardlockRepository cardlockRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final KeyholderInvitationRepository invitationRepository;
|
||||
private final VerificationRepository verificationRepository;
|
||||
private final VerificationVoteRepository verificationVoteRepository;
|
||||
private final CommunityVerificationRepository verificationRepository;
|
||||
private final CommunityVerificationVoteRepository verificationVoteRepository;
|
||||
private final KeyholderNotificationRepository keyholderNotificationRepository;
|
||||
private final LockeeInvitationRepository lockeeInvitationRepository;
|
||||
private final AssignedTaskRepository assignedTaskRepository;
|
||||
@@ -81,16 +80,20 @@ public class CardLockController {
|
||||
@Value("${app.base-url:http://localhost:8080}")
|
||||
private String baseUrl;
|
||||
|
||||
public CardLockController(CardlockRepository cardlockRepository, UserRepository userRepository,
|
||||
KeyholderInvitationRepository invitationRepository, VerificationRepository verificationRepository,
|
||||
VerificationVoteRepository verificationVoteRepository,
|
||||
public CardLockController(CardlockRepository cardlockRepository,
|
||||
UserRepository userRepository,
|
||||
KeyholderInvitationRepository invitationRepository,
|
||||
CommunityVerificationRepository verificationRepository,
|
||||
CommunityVerificationVoteRepository verificationVoteRepository,
|
||||
KeyholderNotificationRepository keyholderNotificationRepository,
|
||||
LockeeInvitationRepository lockeeInvitationRepository, AssignedTaskRepository assignedTaskRepository,
|
||||
LockeeInvitationRepository lockeeInvitationRepository,
|
||||
AssignedTaskRepository assignedTaskRepository,
|
||||
KeyholderTaskChoiceRepository keyholderTaskChoiceRepository,
|
||||
CommunityTaskVoteRepository communityTaskVoteRepository,
|
||||
UnlockCodeHistoryRepository unlockCodeHistoryRepository, UnlockCodeHistoryService unlockCodeHistoryService,
|
||||
|
||||
SystemMessageService systemMessageService, CardLockServiceFactory cardLockServiceFactory) {
|
||||
UnlockCodeHistoryRepository unlockCodeHistoryRepository,
|
||||
UnlockCodeHistoryService unlockCodeHistoryService,
|
||||
SystemMessageService systemMessageService,
|
||||
CardLockServiceFactory cardLockServiceFactory) {
|
||||
this.cardlockRepository = cardlockRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.invitationRepository = invitationRepository;
|
||||
@@ -111,7 +114,7 @@ public class CardLockController {
|
||||
List<CardEnum> initialCards, Integer pickEveryMinute, boolean accumulatePicks, boolean showRemainingCards,
|
||||
LocalDateTime latestOpeningtime, Integer hygineOpeningDurationMinutes, Integer hygineOpeningEveryMinites,
|
||||
List<Task> tasks, boolean requiresVerification, boolean testLock, Integer unlockCodeLines,
|
||||
String taskCardMode) {
|
||||
TaskMode taskMode) {
|
||||
}
|
||||
|
||||
private static final SecureRandom RNG = new SecureRandom();
|
||||
@@ -161,7 +164,7 @@ public class CardLockController {
|
||||
lock.setTasks(req.tasks() != null ? req.tasks() : List.of());
|
||||
lock.setRequiresVerification(req.requiresVerification());
|
||||
lock.setTestLock(false);
|
||||
lock.setTaskCardMode(req.taskCardMode() != null ? req.taskCardMode() : "RANDOM");
|
||||
lock.setTaskMode(req.taskMode() != null ? req.taskMode() : TaskMode.RANDOM);
|
||||
// startTime, unlockCode, unlockCodeLines left null until lockee accepts
|
||||
cardlockRepository.save(lock);
|
||||
|
||||
@@ -204,7 +207,7 @@ public class CardLockController {
|
||||
lock.setTasks(req.tasks() != null ? req.tasks() : List.of());
|
||||
lock.setRequiresVerification(req.requiresVerification());
|
||||
lock.setTestLock(req.testLock());
|
||||
lock.setTaskCardMode(req.taskCardMode() != null ? req.taskCardMode() : "RANDOM");
|
||||
lock.setTaskMode(req.taskMode() != null ? req.taskMode() : TaskMode.RANDOM);
|
||||
lock.setUnlockCodeLength(codeLines);
|
||||
lock.setUnlockCode(unlockCode);
|
||||
|
||||
@@ -267,34 +270,7 @@ public class CardLockController {
|
||||
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", de.oaa.xxx.social.entity.MessageCause.GAME_STATE));
|
||||
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";
|
||||
}
|
||||
}
|
||||
String taskPending = (dto.card() == CardEnum.TASK) ? service.getPendingTaskMode() : null;
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("card", dto.card().name());
|
||||
@@ -455,10 +431,10 @@ public class CardLockController {
|
||||
result.put("totalCards", totalCards);
|
||||
result.put("openPicks", l.getOpenPicks() != null ? l.getOpenPicks() : 0);
|
||||
result.put("nextCardIn", l.getNextCardIn() != null ? l.getNextCardIn().toString() : "");
|
||||
result.put("frozenUntill", l.getFrozenUntill() != null ? l.getFrozenUntill().toString() : null);
|
||||
result.put("frozenUntill", l.getFrozenUntil() != null ? l.getFrozenUntil().toString() : 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("taskFrozenUntil", l.getTaskUntil() != null ? l.getTaskUntil().toString() : null);
|
||||
result.put("hygieneEnabled", hygieneEnabled);
|
||||
result.put("hygieneOpeningDue", hygieneOpeningDue);
|
||||
result.put("hygieneSecondsRemaining", hygieneSecondsRemaining);
|
||||
@@ -489,19 +465,19 @@ public class CardLockController {
|
||||
LocalDateTime todayStart = LocalDate.now().atStartOfDay();
|
||||
LocalDateTime todayEnd = todayStart.plusDays(1);
|
||||
var completed = verificationRepository
|
||||
.findByLockIdAndVerificationTimeBetweenAndImageIsNotNull(l.getLockId(), todayStart, todayEnd);
|
||||
.findByLockIdAndCreatedAtBetweenAndImageIsNotNull(l.getLockId(), todayStart, todayEnd);
|
||||
if (!completed.isEmpty()) {
|
||||
var todayV = completed.get(0);
|
||||
verificationTodayId = todayV.getVerficationId().toString();
|
||||
var votes = verificationVoteRepository.findAllByVerificationId(todayV.getVerficationId());
|
||||
verificationUpvotes = votes.stream().filter(VerificationVoteEntity::isUpvote).count();
|
||||
verificationTodayId = todayV.getDisplayId().toString();
|
||||
var votes = verificationVoteRepository.findAllByVerificationId(todayV.getDisplayId());
|
||||
verificationUpvotes = votes.stream().filter(CommunityVerificationVoteEntity::isUpvote).count();
|
||||
verificationDownvotes = votes.stream().filter(v2 -> !v2.isUpvote()).count();
|
||||
} else {
|
||||
verificationDue = true;
|
||||
var pending = verificationRepository.findByLockIdAndVerificationTimeBetweenAndImageIsNull(l.getLockId(),
|
||||
var pending = verificationRepository.findByLockIdAndCreatedAtBetweenAndImageIsNull(l.getLockId(),
|
||||
todayStart, todayEnd);
|
||||
if (!pending.isEmpty()) {
|
||||
verificationPendingId = pending.get(0).getVerficationId().toString();
|
||||
verificationPendingId = pending.get(0).getDisplayId().toString();
|
||||
verificationPendingCode = pending.get(0).getCode();
|
||||
}
|
||||
}
|
||||
@@ -515,20 +491,19 @@ public class CardLockController {
|
||||
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();
|
||||
if (!expiredTasks.isEmpty()) {
|
||||
CardLockService penaltyService = cardLockServiceFactory.create(l);
|
||||
for (var t : expiredTasks) {
|
||||
t.setStatus("EXPIRED");
|
||||
applyAssignedTaskPenalty(l, t);
|
||||
penaltyService.applyAssignedTaskPenalty(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(), de.oaa.xxx.social.entity.MessageCause.GAME_STATE);
|
||||
}
|
||||
if (lockDirty)
|
||||
cardlockRepository.save(l);
|
||||
}
|
||||
|
||||
// Ausstehende Keyholder-Aufgaben (ohne Aufgabentext)
|
||||
var pendingAssigned = assignedTaskRepository.findByLockIdAndStatus(l.getLockId(), "PENDING").stream()
|
||||
@@ -546,20 +521,20 @@ public class CardLockController {
|
||||
return m;
|
||||
}).toList();
|
||||
result.put("assignedTasks", pendingAssigned);
|
||||
result.put("taskCardMode", l.getTaskCardMode());
|
||||
result.put("taskMode", l.getTaskMode());
|
||||
|
||||
// Ausstehende Keyholder-Choices
|
||||
boolean pendingKeyholderChoice = !keyholderTaskChoiceRepository.findByLockIdAndStatus(l.getLockId(), "PENDING")
|
||||
boolean pendingKeyholderChoice = !keyholderTaskChoiceRepository.findByLockIdAndActiveTrue(l.getLockId())
|
||||
.isEmpty();
|
||||
result.put("pendingKeyholderChoice", pendingKeyholderChoice);
|
||||
|
||||
// Aktive Community-Vote
|
||||
var activeVotes = communityTaskVoteRepository.findByStatus("ACTIVE").stream()
|
||||
var activeVotes = communityTaskVoteRepository.findByActiveTrue().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()));
|
||||
Map.of("voteSessionId", v.getDisplayId().toString(), "expiresAt", v.getExpiresAt().toString()));
|
||||
}
|
||||
|
||||
// Notfall-Entsperrung: nach 1 Stunde automatisch öffnen
|
||||
@@ -608,30 +583,30 @@ public class CardLockController {
|
||||
LocalDateTime todayEnd = todayStart.plusDays(1);
|
||||
|
||||
// Existierende Verifikation für heute zurückgeben statt neue anlegen
|
||||
var existing = verificationRepository.findByLockIdAndVerificationTimeBetweenAndImageIsNull(lockId, todayStart,
|
||||
var existing = verificationRepository.findByLockIdAndCreatedAtBetweenAndImageIsNull(lockId, todayStart,
|
||||
todayEnd);
|
||||
if (!existing.isEmpty()) {
|
||||
var ev = existing.get(0);
|
||||
return ResponseEntity.ok(Map.of("verificationId", ev.getVerficationId().toString(), "code", ev.getCode()));
|
||||
return ResponseEntity.ok(Map.of("verificationId", ev.getDisplayId().toString(), "code", ev.getCode()));
|
||||
}
|
||||
var completed = verificationRepository.findByLockIdAndVerificationTimeBetweenAndImageIsNotNull(lockId,
|
||||
var completed = verificationRepository.findByLockIdAndCreatedAtBetweenAndImageIsNotNull(lockId,
|
||||
todayStart, todayEnd);
|
||||
if (!completed.isEmpty()) {
|
||||
var cv = completed.get(0);
|
||||
return ResponseEntity.ok(Map.of("verificationId", cv.getVerficationId().toString(), "code", cv.getCode()));
|
||||
return ResponseEntity.ok(Map.of("verificationId", cv.getDisplayId().toString(), "code", cv.getCode()));
|
||||
}
|
||||
|
||||
VerificationEntity v = new VerificationEntity();
|
||||
v.setVerficationId(UUID.randomUUID());
|
||||
CommunityVerificationEntity v = new CommunityVerificationEntity();
|
||||
v.setDisplayId(UUID.randomUUID());
|
||||
v.setLockId(lockId);
|
||||
v.setLockeeId(myId);
|
||||
v.setCode(CodeCreator.createAlphanumericCode(6));
|
||||
v.setVerificationTime(LocalDateTime.now());
|
||||
v.setCode(CodeCreator.createAlphanumeric(6));
|
||||
v.setCreatedAt(LocalDateTime.now());
|
||||
if (l.getKeyholder() != null)
|
||||
v.setKeyholderId(l.getKeyholder());
|
||||
verificationRepository.save(v);
|
||||
|
||||
return ResponseEntity.ok(Map.of("verificationId", v.getVerficationId().toString(), "code", v.getCode()));
|
||||
return ResponseEntity.ok(Map.of("verificationId", v.getDisplayId().toString(), "code", v.getCode()));
|
||||
}
|
||||
|
||||
@PostMapping(value = "/cardlock/{lockId}/verification/{verificationId}/complete", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@@ -707,10 +682,10 @@ public class CardLockController {
|
||||
|
||||
LocalDateTime todayStart = LocalDate.now().atStartOfDay();
|
||||
LocalDateTime todayEnd = todayStart.plusDays(1);
|
||||
var completed = verificationRepository.findByLockIdAndVerificationTimeBetweenAndImageIsNotNull(lockId,
|
||||
var completed = verificationRepository.findByLockIdAndCreatedAtBetweenAndImageIsNotNull(lockId,
|
||||
todayStart, todayEnd);
|
||||
for (var v : completed) {
|
||||
verificationVoteRepository.deleteAllByVerificationId(v.getVerficationId());
|
||||
verificationVoteRepository.deleteAllByVerificationId(v.getDisplayId());
|
||||
verificationRepository.delete(v);
|
||||
}
|
||||
return ResponseEntity.noContent().build();
|
||||
@@ -866,7 +841,7 @@ public class CardLockController {
|
||||
item.put("lockeeProfilePic", lockee.getProfilePicture());
|
||||
item.put("totalCards", lock.getAvailableCards() != null ? lock.getAvailableCards().size() : 0);
|
||||
item.put("startTime", lock.getStartTime() != null ? lock.getStartTime().toString() : null);
|
||||
boolean frozenByKh = lock.getFrozenUntill() != null && lock.getFrozenUntill().isAfter(LocalDateTime.now())
|
||||
boolean frozenByKh = lock.getFrozenUntil() != null && lock.getFrozenUntil().isAfter(LocalDateTime.now())
|
||||
&& (lock.getCurrentTask() == null || lock.getCurrentTask().isBlank());
|
||||
item.put("isFrozenByKeyholder", frozenByKh);
|
||||
result.add(item);
|
||||
@@ -919,16 +894,16 @@ public class CardLockController {
|
||||
if (l.isRequiresVerification()) {
|
||||
LocalDateTime todayStart = LocalDate.now().atStartOfDay();
|
||||
LocalDateTime todayEnd = todayStart.plusDays(1);
|
||||
var completed = verificationRepository.findByLockIdAndVerificationTimeBetweenAndImageIsNotNull(lockId,
|
||||
var completed = verificationRepository.findByLockIdAndCreatedAtBetweenAndImageIsNotNull(lockId,
|
||||
todayStart, todayEnd);
|
||||
if (!completed.isEmpty()) {
|
||||
verificationDoneToday = true;
|
||||
var v = completed.get(0);
|
||||
var votes = verificationVoteRepository.findAllByVerificationId(v.getVerficationId());
|
||||
verificationUpvotes = votes.stream().filter(VerificationVoteEntity::isUpvote).count();
|
||||
var votes = verificationVoteRepository.findAllByVerificationId(v.getDisplayId());
|
||||
verificationUpvotes = votes.stream().filter(CommunityVerificationVoteEntity::isUpvote).count();
|
||||
verificationDownvotes = votes.stream().filter(v2 -> !v2.isUpvote()).count();
|
||||
verificationTodayId = v.getVerficationId().toString();
|
||||
var myVoteOpt = verificationVoteRepository.findByVerificationIdAndUserId(v.getVerficationId(), myId);
|
||||
verificationTodayId = v.getDisplayId().toString();
|
||||
var myVoteOpt = verificationVoteRepository.findByVerificationIdAndUserId(v.getDisplayId(), myId);
|
||||
if (myVoteOpt.isPresent()) {
|
||||
verificationMyVote = myVoteOpt.get().isUpvote() ? "upvote" : "downvote";
|
||||
} else if (v.getImage() != null) {
|
||||
@@ -954,9 +929,9 @@ public class CardLockController {
|
||||
result.put("cardCounts", cardCounts);
|
||||
result.put("openPicks", l.getOpenPicks() != null ? l.getOpenPicks() : 0);
|
||||
result.put("nextCardIn", l.getNextCardIn() != null ? l.getNextCardIn().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("frozenUntill", l.getFrozenUntil() != null ? l.getFrozenUntil().toString() : null);
|
||||
result.put("taskFrozenUntil", l.getTaskUntil() != null ? l.getTaskUntil().toString() : null);
|
||||
boolean isFrozenByKeyholder = l.getFrozenUntil() != null && l.getFrozenUntil().isAfter(LocalDateTime.now());
|
||||
result.put("isFrozenByKeyholder", isFrozenByKeyholder);
|
||||
result.put("currentTask", l.getCurrentTask());
|
||||
result.put("currentTaskDescription", l.getCurrentTaskDescription());
|
||||
@@ -978,7 +953,7 @@ public class CardLockController {
|
||||
if (l.getTasks() != null) {
|
||||
var taskList = l.getTasks().stream().map(t -> {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("title", t.resolveTitle());
|
||||
m.put("title", t.getTitle());
|
||||
m.put("description", t.getDescription() != null ? t.getDescription() : "");
|
||||
m.put("minutes", t.getMinutes() != null ? t.getMinutes() : 0);
|
||||
return m;
|
||||
@@ -1002,7 +977,7 @@ public class CardLockController {
|
||||
return m;
|
||||
}).toList();
|
||||
result.put("pendingAssignedTasks", pendingAssigned);
|
||||
result.put("taskCardMode", l.getTaskCardMode());
|
||||
result.put("taskMode", l.getTaskMode());
|
||||
|
||||
// Ausstehende Task-Karten-Choices (KEYHOLDER-Modus)
|
||||
List<Task> lockTasks = l.getTasks() != null ? l.getTasks() : List.of();
|
||||
@@ -1011,12 +986,12 @@ public class CardLockController {
|
||||
Task t = lockTasks.get(i);
|
||||
Map<String, Object> tm = new LinkedHashMap<>();
|
||||
tm.put("index", i);
|
||||
tm.put("title", t.resolveTitle());
|
||||
tm.put("title", t.getTitle());
|
||||
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 -> {
|
||||
var pendingChoices = keyholderTaskChoiceRepository.findByLockIdAndActiveTrue(lockId).stream().map(c -> {
|
||||
Map<String, Object> cm = new LinkedHashMap<>();
|
||||
cm.put("choiceId", c.getChoiceId().toString());
|
||||
cm.put("createdAt", c.getCreatedAt().toString());
|
||||
@@ -1053,7 +1028,7 @@ public class CardLockController {
|
||||
service.unlock(l.getUnlockCode());
|
||||
|
||||
var verifications = verificationRepository.findByLockId(lockId);
|
||||
verifications.forEach(v -> verificationVoteRepository.deleteAllByVerificationId(v.getVerficationId()));
|
||||
verifications.forEach(v -> verificationVoteRepository.deleteAllByVerificationId(v.getDisplayId()));
|
||||
verificationRepository.deleteAll(verifications);
|
||||
invitationRepository.deleteByLockId(lockId);
|
||||
cardlockRepository.deleteById(lockId);
|
||||
@@ -1180,24 +1155,6 @@ public class CardLockController {
|
||||
|
||||
// ── 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,
|
||||
de.oaa.xxx.social.entity.MessageCause cause) {
|
||||
systemMessageService.send(senderId, receiverId, text, targetUrl, cause);
|
||||
@@ -1263,9 +1220,9 @@ public class CardLockController {
|
||||
Task task = tasks.get(req.taskIndex());
|
||||
AssignedTaskEntity assigned = new AssignedTaskEntity();
|
||||
assigned.setLockId(lockId);
|
||||
assigned.setTaskTitle(task.resolveTitle());
|
||||
assigned.setTaskTitle(task.getTitle());
|
||||
assigned.setTaskDescription(task.getDescription());
|
||||
assigned.setTaskText(task.resolveTitle()); // Compat
|
||||
assigned.setTaskText(task.getTitle()); // Compat
|
||||
assigned.setTaskMinutes(task.getMinutes());
|
||||
assigned.setAssignedAt(LocalDateTime.now());
|
||||
assigned.setAcceptDeadline(LocalDateTime.now().plusMinutes(req.acceptDeadlineMinutes()));
|
||||
@@ -1309,14 +1266,13 @@ public class CardLockController {
|
||||
if (task.getAcceptDeadline().isBefore(LocalDateTime.now())) {
|
||||
// Bereits abgelaufen – Strafe anwenden
|
||||
task.setStatus("EXPIRED");
|
||||
applyAssignedTaskPenalty(l, task);
|
||||
cardLockServiceFactory.create(l).applyAssignedTaskPenalty(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()));
|
||||
|| (l.getTaskUntil() != null && l.getTaskUntil().isAfter(LocalDateTime.now()));
|
||||
if (hasActiveTask)
|
||||
return ResponseEntity.status(409).body(Map.of("error", "Du hast bereits eine laufende Aufgabe."));
|
||||
|
||||
@@ -1325,7 +1281,7 @@ public class CardLockController {
|
||||
l.setCurrentTask(title);
|
||||
l.setCurrentTaskDescription(task.getTaskDescription());
|
||||
if (task.getTaskMinutes() != null && task.getTaskMinutes() > 0) {
|
||||
l.setTaskFrozenUntil(LocalDateTime.now().plusMinutes(task.getTaskMinutes()));
|
||||
l.setTaskUntil(LocalDateTime.now().plusMinutes(task.getTaskMinutes()));
|
||||
|
||||
// Fälligkeit aller anderen offenen Aufgaben um die Task-Dauer verschieben
|
||||
final int extraMinutes = task.getTaskMinutes();
|
||||
@@ -1370,9 +1326,8 @@ public class CardLockController {
|
||||
return ResponseEntity.status(409).body(Map.of("error", "Diese Aufgabe ist nicht mehr ausstehend."));
|
||||
|
||||
task.setStatus("DECLINED");
|
||||
applyAssignedTaskPenalty(l, task);
|
||||
cardLockServiceFactory.create(l).applyAssignedTaskPenalty(task);
|
||||
assignedTaskRepository.save(task);
|
||||
cardlockRepository.save(l);
|
||||
|
||||
sendMessage(myId, l.getKeyholder(),
|
||||
meOpt.get().getName() + " hat die gestellte Aufgabe abgelehnt. Die Strafe wurde angewendet.",
|
||||
@@ -1443,7 +1398,7 @@ public class CardLockController {
|
||||
return ResponseEntity.badRequest().body(Map.of("error", "Zeitpunkt muss in der Zukunft liegen."));
|
||||
}
|
||||
|
||||
l.setFrozenUntill(until);
|
||||
l.setFrozenUntil(until);
|
||||
cardlockRepository.save(l);
|
||||
|
||||
sendMessage(myId, l.getLockee(),
|
||||
@@ -1476,7 +1431,7 @@ public class CardLockController {
|
||||
"Das Lock ist durch eine Aufgabe eingefroren und kann nicht manuell entfroren werden."));
|
||||
}
|
||||
|
||||
l.setFrozenUntill(null);
|
||||
l.setFrozenUntil(null);
|
||||
cardlockRepository.save(l);
|
||||
|
||||
sendMessage(myId, l.getLockee(), me.getName() + " hat dein Lock wieder entfroren.",
|
||||
|
||||
@@ -2,40 +2,24 @@ package de.oaa.xxx.games.chastity.cardlock;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.oaa.xxx.games.chastity.common.BaseLockEntity;
|
||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||
import de.oaa.xxx.games.chastity.tasks.TaskListConverter;
|
||||
import de.oaa.xxx.games.chastity.unlock.TempOpeningReason;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Convert;
|
||||
import jakarta.persistence.DiscriminatorValue;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "card_lock")
|
||||
public class CardLockEntity {
|
||||
@DiscriminatorValue("CARDLOCK")
|
||||
public class CardLockEntity extends BaseLockEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
@Column
|
||||
private UUID lockId;
|
||||
|
||||
@Column
|
||||
private String name;
|
||||
|
||||
// Settings
|
||||
@Column(nullable = false)
|
||||
private UUID lockee;
|
||||
@Column
|
||||
private UUID keyholder;
|
||||
@Convert(converter = CardEnumListConverter.class)
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private List<CardEnum> initialCards;
|
||||
@@ -47,69 +31,17 @@ public class CardLockEntity {
|
||||
private boolean showRemainingCards;
|
||||
@Column
|
||||
private LocalDateTime latestOpeningtime;
|
||||
@Column
|
||||
private LocalDateTime frozenUntill;
|
||||
@Column
|
||||
private Integer hygineOpeningDurationMinutes;
|
||||
@Column
|
||||
private Integer hygineOpeningEveryMinites;
|
||||
@Convert(converter = TaskListConverter.class)
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private List<Task> tasks;
|
||||
@Column
|
||||
private boolean requiresVerification;
|
||||
@Column
|
||||
private boolean testLock;
|
||||
@Column
|
||||
private Integer unlockCodeLength;
|
||||
|
||||
// State
|
||||
@Column
|
||||
private LocalDateTime startTime;
|
||||
@Column
|
||||
private LocalDateTime nextCardIn;
|
||||
@Column
|
||||
private Integer openPicks;
|
||||
@Convert(converter = CardEnumListConverter.class)
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private List<CardEnum> availableCards;
|
||||
@Column
|
||||
private LocalDateTime lastHygineOpening;
|
||||
@Column
|
||||
private LocalDateTime tempOpeningTime; // If null, not while hygine opening
|
||||
@Column
|
||||
private Integer tempOpeningDuration;
|
||||
@Column
|
||||
private TempOpeningReason tempOpeningReason;
|
||||
@Column
|
||||
private LocalDateTime unlockTime;
|
||||
@Column
|
||||
private String currentTask;
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String currentTaskDescription;
|
||||
@Column
|
||||
private LocalDateTime taskFrozenUntil;
|
||||
|
||||
@Convert(converter = TaskListConverter.class)
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private List<Task> tasksInQueue;
|
||||
@Column
|
||||
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 String getTaskCardMode() { return taskCardMode != null ? taskCardMode : "RANDOM"; }
|
||||
}
|
||||
|
||||
@@ -1,57 +1,91 @@
|
||||
package de.oaa.xxx.games.chastity.cardlock;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import de.oaa.xxx.games.chastity.common.AbstractLockService;
|
||||
import de.oaa.xxx.games.chastity.common.BaseLockEntity;
|
||||
import de.oaa.xxx.games.chastity.common.BaseLockService;
|
||||
import de.oaa.xxx.games.chastity.common.CodeCreator;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationEntity;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityTaskVoteRepository;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationVoteRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationRepository;
|
||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderTaskChoiceRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderVerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.tasks.AssignedTaskEntity;
|
||||
import de.oaa.xxx.games.chastity.unlock.TempOpeningReason;
|
||||
import de.oaa.xxx.games.chastity.unlock.UnlockCodeHistoryService;
|
||||
import de.oaa.xxx.games.chastity.verification.VerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.verification.VerificationVoteRepository;
|
||||
import de.oaa.xxx.games.history.GameHistoryEntity;
|
||||
import de.oaa.xxx.games.history.GameHistoryRepository;
|
||||
import de.oaa.xxx.games.history.GameType;
|
||||
import de.oaa.xxx.social.SystemMessageService;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
|
||||
public class CardLockService extends AbstractLockService {
|
||||
public class CardLockService extends BaseLockService {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CardLockService.class);
|
||||
private final CardLockEntity lock;
|
||||
private CardLockRepository cardLockRepository;
|
||||
private VerificationRepository verificationRepository;
|
||||
private GameHistoryRepository gameHistoryRepository;
|
||||
private UserRepository userRepository;
|
||||
private UnlockCodeHistoryService unlockCodeHistoryService;
|
||||
private KeyholderNotificationRepository keyholderNotificationRepository;
|
||||
private final CardLockRepository cardLockRepository;
|
||||
private String pendingTaskMode;
|
||||
|
||||
public CardLockService(CardLockEntity lock, VerificationRepository verificationRepository,
|
||||
VerificationVoteRepository verificationVoteRepository, CardLockRepository cardLockRepository,
|
||||
GameHistoryRepository gameHistoryRepository, UserRepository userRepository, KeyholderNotificationRepository keyholderNotificationRepository,UnlockCodeHistoryService unlockCodeHistoryService) {
|
||||
super(verificationVoteRepository);
|
||||
public CardLockService(
|
||||
CardLockEntity lock,
|
||||
CommunityVerificationVoteRepository communityVerificationVoteRepository,
|
||||
CommunityVerificationRepository communityVerificationRepository,
|
||||
KeyholderVerificationRepository keyholderVerificationRepository,
|
||||
GameHistoryRepository gameHistoryRepository,
|
||||
UserRepository userRepository,
|
||||
KeyholderNotificationRepository keyholderNotificationRepository,
|
||||
SystemMessageService systemMessageService,
|
||||
UnlockCodeHistoryService unlockCodeHistoryService,
|
||||
KeyholderTaskChoiceRepository keyholderTaskChoiceRepository,
|
||||
CommunityTaskVoteRepository communityTaskVoteRepository,
|
||||
CardLockRepository cardLockRepository) {
|
||||
super(communityVerificationVoteRepository, communityVerificationRepository, keyholderVerificationRepository,
|
||||
gameHistoryRepository, userRepository, keyholderNotificationRepository, systemMessageService,
|
||||
unlockCodeHistoryService, keyholderTaskChoiceRepository, communityTaskVoteRepository);
|
||||
this.lock = lock;
|
||||
this.cardLockRepository = cardLockRepository;
|
||||
this.verificationRepository = verificationRepository;
|
||||
this.gameHistoryRepository = gameHistoryRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.keyholderNotificationRepository = keyholderNotificationRepository;
|
||||
this.unlockCodeHistoryService = unlockCodeHistoryService;
|
||||
}
|
||||
|
||||
// ── Abstract method implementations ──────────────────────────────────────
|
||||
|
||||
@Override
|
||||
protected BaseLockEntity getLock() {
|
||||
return lock;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveLock() {
|
||||
cardLockRepository.save(lock);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GameType getGameType() {
|
||||
return GameType.CARDLOCK;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyHygieneOvertime(Long overtime) {
|
||||
if (lock.getFrozenUntil() != null) {
|
||||
lock.setFrozenUntil(lock.getFrozenUntil().plusMinutes(overtime * 4));
|
||||
} else {
|
||||
lock.setFrozenUntil(LocalDateTime.now().plusMinutes(overtime * 4));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Card drawing ──────────────────────────────────────────────────────────
|
||||
|
||||
public CardDTO getNextCard() {
|
||||
LOGGER.debug("New Card requested by user {}", lock.getLockee());
|
||||
CardDTO card = null;
|
||||
if (lock.isKeyholderRequestedUnlock() || (lock.getLatestOpeningtime() != null && lock.getLatestOpeningtime().isAfter(LocalDateTime.now()))) {
|
||||
if (lock.isKeyholderRequestedUnlock()
|
||||
|| (lock.getLatestOpeningtime() != null && lock.getLatestOpeningtime().isAfter(LocalDateTime.now()))) {
|
||||
card = getGreenCard();
|
||||
} else if (lock.isAccumulatePicks()) {
|
||||
if (lock.getNextCardIn().isBefore(LocalDateTime.now())) {
|
||||
@@ -87,6 +121,8 @@ public class CardLockService extends AbstractLockService {
|
||||
return new CardDTO(CardEnum.GREEN, lock.getUnlockCode());
|
||||
}
|
||||
|
||||
// ── Card effects ──────────────────────────────────────────────────────────
|
||||
|
||||
public String doubleUp() {
|
||||
var cards = lock.getAvailableCards();
|
||||
LOGGER.debug("Double up {} cards", cards.size());
|
||||
@@ -106,74 +142,6 @@ public class CardLockService extends AbstractLockService {
|
||||
return lock.getUnlockCode();
|
||||
}
|
||||
|
||||
public void unlock(String unlockCode) {
|
||||
this.lock.setUnlockTime(LocalDateTime.now());
|
||||
boolean valid = true;
|
||||
if (lock.isEmergencyAutoUnlocked()) {
|
||||
valid = false;
|
||||
LOGGER.debug("Lock invalid - Emergency Auto-Unlock (1h timer)");
|
||||
}
|
||||
if (lock.isTestLock()) {
|
||||
valid = false;
|
||||
} else if (Duration.between(lock.getStartTime(), lock.getUnlockTime()).toHours() > 24) {
|
||||
Set<LocalDate> verifications = verificationRepository.findByLockId(this.lock.getLockId()).stream()
|
||||
.filter(verification -> isValid(verification))
|
||||
.map(verification -> verification.getVerificationTime().toLocalDate())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
LocalDate current = this.lock.getStartTime().toLocalDate();
|
||||
LocalDate last = this.lock.getUnlockTime().toLocalDate().minusDays(1);
|
||||
|
||||
while (!current.isAfter(last)) {
|
||||
if (!verifications.contains(current)) {
|
||||
valid = false;
|
||||
LOGGER.debug("Lock invalid - no daily verification on %s", current.toString());
|
||||
break;
|
||||
}
|
||||
current = current.plusDays(1);
|
||||
}
|
||||
}
|
||||
|
||||
lock.setUnlockTime(LocalDateTime.now());
|
||||
LOGGER.debug("Unlocked at {}", lock.getUnlockTime());
|
||||
cardLockRepository.save(lock);
|
||||
|
||||
if (valid) {
|
||||
long durationMinutes = Duration.between(lock.getStartTime(), lock.getUnlockTime()).toMinutes();
|
||||
|
||||
// Gemeinsamer History-Eintrag mit Teilnehmerliste
|
||||
GameHistoryEntity entry = new GameHistoryEntity();
|
||||
entry.setGameType(de.oaa.xxx.games.history.GameType.CARDLOCK);
|
||||
entry.setGameName(lock.getName());
|
||||
entry.setStartTime(lock.getStartTime());
|
||||
entry.setEndTime(lock.getUnlockTime());
|
||||
entry.setDurationMinutes(durationMinutes);
|
||||
entry.addParticipant(lock.getLockee(), de.oaa.xxx.games.history.GameRole.LOCKEE);
|
||||
if (lock.getKeyholder() != null) {
|
||||
entry.addParticipant(lock.getKeyholder(), de.oaa.xxx.games.history.GameRole.KEYHOLDER);
|
||||
}
|
||||
gameHistoryRepository.save(entry);
|
||||
|
||||
int minutes = (int) durationMinutes;
|
||||
userRepository.findById(lock.getLockee()).ifPresent(u -> {
|
||||
u.setLockeeXp(u.getLockeeXp() + minutes);
|
||||
userRepository.save(u);
|
||||
});
|
||||
if (lock.getKeyholder() != null) {
|
||||
userRepository.findById(lock.getKeyholder()).ifPresent(u -> {
|
||||
u.setKeyholderXp(u.getKeyholderXp() + minutes);
|
||||
userRepository.save(u);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void putBackGreen() {
|
||||
LOGGER.debug("Green Card was put Back");
|
||||
lock.getAvailableCards().add(CardEnum.GREEN);
|
||||
cardLockRepository.save(lock);
|
||||
}
|
||||
|
||||
public String freeze() {
|
||||
var multiplier = lock.getPickEveryMinute() * new Random().nextDouble(1.0, 4.0);
|
||||
freeze(multiplier);
|
||||
@@ -182,42 +150,32 @@ public class CardLockService extends AbstractLockService {
|
||||
|
||||
private String freeze(double multiplier) {
|
||||
LocalDateTime frozenTill = LocalDateTime.now().plus((long) multiplier, ChronoUnit.MINUTES);
|
||||
lock.setFrozenUntill(frozenTill);
|
||||
lock.setFrozenUntil(frozenTill);
|
||||
lock.setNextCardIn(frozenTill);
|
||||
LOGGER.debug("Frozen until {}", lock.getFrozenUntill());
|
||||
LOGGER.debug("Frozen until {}", lock.getFrozenUntil());
|
||||
return "";
|
||||
}
|
||||
|
||||
/** Called by TaskCard. Dispatches based on TaskMode and stores result for controller. */
|
||||
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 "";
|
||||
switch (lock.getTaskMode()) {
|
||||
case RANDOM -> applyRandomTask();
|
||||
case KEYHOLDER -> {
|
||||
if (lock.isTestLock()) applyRandomTask();
|
||||
else startKeyholderVote();
|
||||
}
|
||||
LOGGER.debug("Apply random task");
|
||||
var tasks = lock.getTasks();
|
||||
if (!tasks.isEmpty()) {
|
||||
task(tasks.get(new Random().nextInt(tasks.size())));
|
||||
case COMMUNITY -> {
|
||||
if (lock.isTestLock()) applyRandomTask();
|
||||
else startCommunityVote();
|
||||
}
|
||||
}
|
||||
pendingTaskMode = lock.getTaskMode().name();
|
||||
return "";
|
||||
}
|
||||
|
||||
public String task(Task task) {
|
||||
LOGGER.debug("Apply task {}", task);
|
||||
lock.setCurrentTask(task.resolveTitle());
|
||||
lock.setCurrentTaskDescription(task.getDescription());
|
||||
if (task.getMinutes() != null && task.getMinutes() > 0) {
|
||||
lock.setTaskFrozenUntil(LocalDateTime.now().plusMinutes(task.getMinutes()));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public String clearTask() {
|
||||
LOGGER.debug("Clear task");
|
||||
lock.setCurrentTask(null);
|
||||
lock.setCurrentTaskDescription(null);
|
||||
lock.setTaskFrozenUntil(null);
|
||||
return "";
|
||||
/** Returns the TaskMode that was triggered by the last task() call, or null if no task card was drawn. */
|
||||
public String getPendingTaskMode() {
|
||||
return pendingTaskMode;
|
||||
}
|
||||
|
||||
public String redCard() {
|
||||
@@ -240,73 +198,23 @@ public class CardLockService extends AbstractLockService {
|
||||
return "";
|
||||
}
|
||||
|
||||
public void putBackGreen() {
|
||||
LOGGER.debug("Green Card was put Back");
|
||||
lock.getAvailableCards().add(CardEnum.GREEN);
|
||||
cardLockRepository.save(lock);
|
||||
}
|
||||
|
||||
// ── Hygiene opening ───────────────────────────────────────────────────────
|
||||
|
||||
public void startHygieneOpening() {
|
||||
tempOperning(TempOpeningReason.HYGIENE, lock.getHygineOpeningDurationMinutes());
|
||||
startTempOpening(TempOpeningReason.HYGIENE, lock.getHygineOpeningDurationMinutes());
|
||||
}
|
||||
|
||||
private Long calcOvertime() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
Long overtime = null;
|
||||
if (lock.getTempOpeningTime() != null && lock.getTempOpeningDuration() != null) {
|
||||
LocalDateTime dueTime = lock.getTempOpeningTime().plusMinutes(lock.getTempOpeningDuration());
|
||||
if (LocalDateTime.now().isAfter(dueTime)) {
|
||||
overtime = ChronoUnit.MINUTES.between(dueTime, now);
|
||||
}
|
||||
}
|
||||
return overtime;
|
||||
}
|
||||
|
||||
public String endHygieneOpening() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
Long overtime = calcOvertime();
|
||||
if (overtime != null) {
|
||||
if (lock.getKeyholder() != null) {
|
||||
reportKeyholder(overtime);
|
||||
}
|
||||
freezeForOvertime(overtime);
|
||||
|
||||
}
|
||||
lock.setLastHygineOpening(now);
|
||||
lock.setTempOpeningDuration(null);
|
||||
lock.setTempOpeningTime(null);
|
||||
|
||||
var code = CodeCreator.createAlphanumericCode(lock.getUnlockCodeLength());
|
||||
lock.setUnlockCode(code);
|
||||
cardLockRepository.save(lock);
|
||||
return code;
|
||||
}
|
||||
|
||||
private void reportKeyholder(Long overtime) {
|
||||
KeyholderNotificationEntity notification = new KeyholderNotificationEntity();
|
||||
notification.setLockId(lock.getLockId());
|
||||
notification.setLockeeId(lock.getLockee());
|
||||
notification.setKeyholderUserId(lock.getKeyholder());
|
||||
notification.setViolationTime(LocalDateTime.now());
|
||||
notification.setOvertimeMinutes(overtime);
|
||||
keyholderNotificationRepository.save(notification);
|
||||
}
|
||||
|
||||
private void freezeForOvertime(Long overtime) {
|
||||
if (lock.getFrozenUntill() != null) {
|
||||
lock.setFrozenUntill(lock.getFrozenUntill().plusMinutes(overtime * 4));
|
||||
} else {
|
||||
lock.setFrozenUntill(LocalDateTime.now().plusMinutes(overtime * 4));
|
||||
}
|
||||
}
|
||||
|
||||
private void tempOperning(TempOpeningReason reason, Integer duration) {
|
||||
assert duration != null;
|
||||
lock.setTempOpeningReason(reason);
|
||||
lock.setTempOpeningTime(LocalDateTime.now());;
|
||||
lock.setTempOpeningDuration(duration);
|
||||
cardLockRepository.save(lock);
|
||||
unlockCodeHistoryService.save(lock.getLockee(), lock.getLockId(), lock.getName(), lock.getUnlockCode(), reason.toString());
|
||||
}
|
||||
// ── Cum cards ─────────────────────────────────────────────────────────────
|
||||
|
||||
public String cum(boolean tempUnlock) {
|
||||
if (tempUnlock) {
|
||||
tempOperning(TempOpeningReason.CARD, 0); // Je länger man braucht, desto länger wird gefreezed
|
||||
startTempOpening(TempOpeningReason.CARD, 0);
|
||||
}
|
||||
return lock.getUnlockCode();
|
||||
}
|
||||
@@ -315,7 +223,7 @@ public class CardLockService extends AbstractLockService {
|
||||
Long overtime = calcOvertime();
|
||||
if (overtime != null) {
|
||||
if (lock.getKeyholder() == null) {
|
||||
freezeForOvertime(overtime);
|
||||
applyHygieneOvertime(overtime);
|
||||
} else {
|
||||
reportKeyholder(overtime);
|
||||
}
|
||||
@@ -324,9 +232,30 @@ public class CardLockService extends AbstractLockService {
|
||||
lock.setTempOpeningTime(null);
|
||||
lock.setTempOpeningReason(null);
|
||||
|
||||
var code = CodeCreator.createAlphanumericCode(lock.getUnlockCodeLength());
|
||||
var code = CodeCreator.createNumeric(lock.getUnlockCodeLength());
|
||||
lock.setUnlockCode(code);
|
||||
cardLockRepository.save(lock);
|
||||
return code;
|
||||
}
|
||||
|
||||
// ── Assigned task penalty ─────────────────────────────────────────────────
|
||||
|
||||
public void applyAssignedTaskPenalty(AssignedTaskEntity task) {
|
||||
if (task.getPenaltyFreezeMinutes() != null && task.getPenaltyFreezeMinutes() > 0) {
|
||||
LocalDateTime until = LocalDateTime.now().plusMinutes(task.getPenaltyFreezeMinutes());
|
||||
if (lock.getFrozenUntil() == null || until.isAfter(lock.getFrozenUntil())) {
|
||||
lock.setFrozenUntil(until);
|
||||
lock.setNextCardIn(until);
|
||||
}
|
||||
}
|
||||
if (task.getPenaltyRedCards() != null && task.getPenaltyRedCards() > 0) {
|
||||
List<CardEnum> cards = new ArrayList<>(
|
||||
lock.getAvailableCards() != null ? lock.getAvailableCards() : List.of());
|
||||
for (int i = 0; i < task.getPenaltyRedCards(); i++) {
|
||||
cards.add(CardEnum.RED);
|
||||
}
|
||||
lock.setAvailableCards(cards);
|
||||
}
|
||||
cardLockRepository.save(lock);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package de.oaa.xxx.games.chastity.cardlock;
|
||||
|
||||
import de.oaa.xxx.games.history.GameHistoryRepository;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityTaskVoteRepository;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationVoteRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderTaskChoiceRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderVerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.unlock.UnlockCodeHistoryService;
|
||||
import de.oaa.xxx.games.chastity.verification.VerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.verification.VerificationVoteRepository;
|
||||
import de.oaa.xxx.social.SystemMessageService;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -18,43 +22,50 @@ import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class CardLockServiceFactory {
|
||||
|
||||
private final VerificationRepository verificationRepository;
|
||||
private final VerificationVoteRepository verificationVoteRepository;
|
||||
private final CardLockRepository cardLockRepository;
|
||||
private final CommunityVerificationRepository communityVerificationRepository;
|
||||
private final CommunityVerificationVoteRepository communityVerificationVoteRepository;
|
||||
private final GameHistoryRepository gameHistoryRepository;
|
||||
private final UserRepository userRepository;
|
||||
private KeyholderNotificationRepository keyholderNotificationRepository;
|
||||
private final UnlockCodeHistoryService unlockCodeHistoryService;
|
||||
private final KeyholderNotificationRepository keyholderNotificationRepository;
|
||||
private final KeyholderVerificationRepository keyholderVerificationRepository;
|
||||
private final SystemMessageService systemMessageService;
|
||||
private final KeyholderTaskChoiceRepository keyholderTaskChoiceRepository;
|
||||
private final CommunityTaskVoteRepository communityTaskVoteRepository;
|
||||
|
||||
public CardLockServiceFactory(VerificationRepository verificationRepository,
|
||||
VerificationVoteRepository verificationVoteRepository,
|
||||
public CardLockServiceFactory(
|
||||
CommunityVerificationRepository communityVerificationRepository,
|
||||
CommunityVerificationVoteRepository communityVerificationVoteRepository,
|
||||
CardLockRepository cardLockRepository,
|
||||
GameHistoryRepository gameHistoryRepository,
|
||||
UserRepository userRepository,
|
||||
KeyholderNotificationRepository keyholderNotificationRepository,
|
||||
UnlockCodeHistoryService unlockCodeHistoryService) {
|
||||
this.verificationRepository = verificationRepository;
|
||||
this.verificationVoteRepository = verificationVoteRepository;
|
||||
KeyholderVerificationRepository keyholderVerificationRepository,
|
||||
UnlockCodeHistoryService unlockCodeHistoryService,
|
||||
SystemMessageService systemMessageService,
|
||||
KeyholderTaskChoiceRepository keyholderTaskChoiceRepository,
|
||||
CommunityTaskVoteRepository communityTaskVoteRepository) {
|
||||
this.cardLockRepository = cardLockRepository;
|
||||
this.communityVerificationRepository = communityVerificationRepository;
|
||||
this.communityVerificationVoteRepository = communityVerificationVoteRepository;
|
||||
this.gameHistoryRepository = gameHistoryRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.keyholderNotificationRepository = keyholderNotificationRepository;
|
||||
this.unlockCodeHistoryService = unlockCodeHistoryService;
|
||||
this.keyholderVerificationRepository = keyholderVerificationRepository;
|
||||
this.systemMessageService = systemMessageService;
|
||||
this.keyholderTaskChoiceRepository = keyholderTaskChoiceRepository;
|
||||
this.communityTaskVoteRepository = communityTaskVoteRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine neue CardLockService-Instanz für das gegebene Lock.
|
||||
*/
|
||||
public CardLockService create(CardLockEntity lock) {
|
||||
return new CardLockService(
|
||||
lock,
|
||||
verificationRepository,
|
||||
verificationVoteRepository,
|
||||
cardLockRepository,
|
||||
gameHistoryRepository,
|
||||
userRepository,
|
||||
keyholderNotificationRepository,
|
||||
unlockCodeHistoryService
|
||||
);
|
||||
return new CardLockService(lock, communityVerificationVoteRepository, communityVerificationRepository,
|
||||
keyholderVerificationRepository, gameHistoryRepository, userRepository,
|
||||
keyholderNotificationRepository, systemMessageService, unlockCodeHistoryService,
|
||||
keyholderTaskChoiceRepository, communityTaskVoteRepository, cardLockRepository);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.oaa.xxx.games.chastity.cardlock;
|
||||
|
||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||
import de.oaa.xxx.games.chastity.tasks.TaskMode;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -33,7 +34,7 @@ public class CardlockTemplateController {
|
||||
Integer hygineOpeningEveryMinites,
|
||||
List<Task> tasks,
|
||||
boolean requiresVerification,
|
||||
String taskCardMode
|
||||
TaskMode taskMode
|
||||
) {}
|
||||
|
||||
private Map<String, Object> toDto(CardlockTemplateEntity t) {
|
||||
@@ -129,6 +130,6 @@ public class CardlockTemplateController {
|
||||
t.setHygineOpeningDurationMinutes(req.hygineOpeningDurationMinutes());
|
||||
t.setTasks(req.tasks() != null ? req.tasks() : List.of());
|
||||
t.setRequiresVerification(req.requiresVerification());
|
||||
t.setTaskCardMode(req.taskCardMode() != null ? req.taskCardMode() : "RANDOM");
|
||||
t.setTaskMode(req.taskMode() != null ? req.taskMode() : TaskMode.RANDOM);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,64 +1,34 @@
|
||||
package de.oaa.xxx.games.chastity.cardlock;
|
||||
|
||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||
import de.oaa.xxx.games.chastity.tasks.TaskListConverter;
|
||||
import jakarta.persistence.*;
|
||||
import java.util.Map;
|
||||
|
||||
import de.oaa.xxx.games.chastity.common.BaseLockTemplateEntity;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Convert;
|
||||
import jakarta.persistence.DiscriminatorValue;
|
||||
import jakarta.persistence.Entity;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "cardlock_template")
|
||||
public class CardlockTemplateEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
@Column
|
||||
private UUID templateId;
|
||||
|
||||
@Column(nullable = false)
|
||||
private UUID owner;
|
||||
|
||||
@Column
|
||||
private String name;
|
||||
@DiscriminatorValue("CARDLOCK")
|
||||
public class CardlockTemplateEntity extends BaseLockTemplateEntity {
|
||||
|
||||
@Convert(converter = CardCountMapConverter.class)
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private Map<String, Integer> cardCountsMin;
|
||||
|
||||
@Convert(converter = CardCountMapConverter.class)
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private Map<String, Integer> cardCountsMax;
|
||||
|
||||
@Column
|
||||
private Integer pickEveryMinute;
|
||||
|
||||
@Column
|
||||
private boolean accumulatePicks;
|
||||
|
||||
@Column
|
||||
private boolean showRemainingCards;
|
||||
|
||||
@Column
|
||||
private Integer hygineOpeningDurationMinutes;
|
||||
|
||||
@Column
|
||||
private Integer hygineOpeningEveryMinites;
|
||||
|
||||
@Convert(converter = TaskListConverter.class)
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private List<Task> tasks;
|
||||
|
||||
@Column
|
||||
private boolean requiresVerification;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String taskCardMode = "RANDOM";
|
||||
|
||||
public String getTaskCardMode() { return taskCardMode != null ? taskCardMode : "RANDOM"; }
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package de.oaa.xxx.games.chastity.common;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import de.oaa.xxx.games.chastity.verification.VerificationEntity;
|
||||
import de.oaa.xxx.games.chastity.verification.VerificationVoteEntity;
|
||||
import de.oaa.xxx.games.chastity.verification.VerificationVoteRepository;
|
||||
|
||||
public abstract class AbstractLockService {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractLockService.class);
|
||||
private final VerificationVoteRepository verificationVoteRepository;
|
||||
|
||||
public AbstractLockService(VerificationVoteRepository verificationVoteRepository) {
|
||||
this.verificationVoteRepository = verificationVoteRepository;
|
||||
}
|
||||
|
||||
protected boolean isValid(VerificationEntity entity) {
|
||||
LOGGER.trace("isValid");
|
||||
int count = 0;
|
||||
for (VerificationVoteEntity vote : verificationVoteRepository.findAllByVerificationId(entity.getVerficationId())) {
|
||||
if (vote.isUpvote()) {
|
||||
count++;
|
||||
} else {
|
||||
count--;
|
||||
}
|
||||
}
|
||||
return count >= 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package de.oaa.xxx.games.chastity.common;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("games/chastity/lock/")
|
||||
public class BaseLockController {
|
||||
|
||||
@GetMapping("/{lockId}/tasks")
|
||||
public ResponseEntity<List<Task>> getTasks() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package de.oaa.xxx.games.chastity.common;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||
import de.oaa.xxx.games.chastity.tasks.TaskListConverter;
|
||||
import de.oaa.xxx.games.chastity.tasks.TaskMode;
|
||||
import de.oaa.xxx.games.chastity.unlock.TempOpeningReason;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Convert;
|
||||
import jakarta.persistence.DiscriminatorColumn;
|
||||
import jakarta.persistence.DiscriminatorType;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Inheritance;
|
||||
import jakarta.persistence.InheritanceType;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "current_lock")
|
||||
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
|
||||
@DiscriminatorColumn(name = "lock_type", discriminatorType = DiscriminatorType.STRING)
|
||||
public class BaseLockEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
@Column
|
||||
private UUID lockId;
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
// --- Gemeinsame Settings ---
|
||||
@Column(nullable = false)
|
||||
private UUID lockee;
|
||||
@Column
|
||||
private UUID keyholder;
|
||||
@Column(nullable = false)
|
||||
private boolean testLock;
|
||||
@Column
|
||||
private boolean requiresVerification;
|
||||
@Column
|
||||
private Integer unlockCodeLength;
|
||||
@Column
|
||||
private String unlockCode;
|
||||
|
||||
// --- Timing & Hygiene ---
|
||||
@Column
|
||||
private LocalDateTime startTime;
|
||||
@Column
|
||||
private LocalDateTime unlockTime;
|
||||
@Column
|
||||
private LocalDateTime lastHygineOpening;
|
||||
@Column
|
||||
private Integer hygineOpeningDurationMinutes;
|
||||
@Column
|
||||
private Integer hygineOpeningEveryMinites;
|
||||
@Column
|
||||
private LocalDateTime tempOpeningTime; // If null, not while hygine opening
|
||||
@Column
|
||||
private Integer tempOpeningDuration;
|
||||
@Column
|
||||
private TempOpeningReason tempOpeningReason;
|
||||
@Column
|
||||
private LocalDateTime frozenUntil;
|
||||
|
||||
// --- Aufgaben-System (Basis) ---
|
||||
@Convert(converter = TaskListConverter.class)
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private List<Task> tasks;
|
||||
@Column
|
||||
private String currentTask;
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String currentTaskDescription;
|
||||
@Column
|
||||
private LocalDateTime taskUntil;
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
private TaskMode taskMode = TaskMode.RANDOM;
|
||||
|
||||
// --- Notfall- & Keyholder-Status ---
|
||||
@Column(nullable = false)
|
||||
private boolean keyholderRequestedUnlock = false;
|
||||
@Column
|
||||
private LocalDateTime emergencyUnlockRequestedAt;
|
||||
@Column(nullable = false)
|
||||
private boolean emergencyAutoUnlocked = false;
|
||||
|
||||
// Getter & Setter
|
||||
public TaskMode getTaskMode() {
|
||||
return taskMode != null ? taskMode : TaskMode.RANDOM;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.oaa.xxx.games.chastity.common;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface BaseLockRepository extends JpaRepository<BaseLockEntity, UUID>{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
package de.oaa.xxx.games.chastity.common;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import de.oaa.xxx.games.chastity.community.CommunityTaskVoteEntity;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityTaskVoteRepository;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationVoteRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationEntity;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderTaskChoiceEntity;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderTaskChoiceRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderVerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||
import de.oaa.xxx.games.chastity.unlock.TempOpeningReason;
|
||||
import de.oaa.xxx.games.chastity.unlock.UnlockCodeHistoryService;
|
||||
import de.oaa.xxx.games.history.GameHistoryEntity;
|
||||
import de.oaa.xxx.games.history.GameHistoryRepository;
|
||||
import de.oaa.xxx.games.history.GameRole;
|
||||
import de.oaa.xxx.games.history.GameType;
|
||||
import de.oaa.xxx.social.SystemMessageService;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
|
||||
public abstract class BaseLockService {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(BaseLockService.class);
|
||||
|
||||
protected final CommunityVerificationVoteRepository communityVerificationVoteRepository;
|
||||
protected final CommunityVerificationRepository communityVerificationRepository;
|
||||
protected final KeyholderVerificationRepository keyholderVerificationRepository;
|
||||
protected final GameHistoryRepository gameHistoryRepository;
|
||||
protected final UserRepository userRepository;
|
||||
protected final KeyholderNotificationRepository keyholderNotificationRepository;
|
||||
protected final SystemMessageService systemMessageService;
|
||||
protected final UnlockCodeHistoryService unlockCodeHistoryService;
|
||||
protected final KeyholderTaskChoiceRepository keyholderTaskChoiceRepository;
|
||||
protected final CommunityTaskVoteRepository communityTaskVoteRepository;
|
||||
|
||||
// ── Abstrakte Methoden ────────────────────────────────────────────────────
|
||||
|
||||
protected abstract BaseLockEntity getLock();
|
||||
protected abstract void saveLock();
|
||||
protected abstract GameType getGameType();
|
||||
/** Wie wird Überschreitung der Hygiene-Öffnung bestraft? CardLock friert ein, TimeLock verlängert die Zeit. */
|
||||
protected abstract void applyHygieneOvertime(Long overtime);
|
||||
|
||||
// ── Hook-Methoden (Standard: No-Op) ───────────────────────────────────────
|
||||
|
||||
/** TimeLock: lockControl.unlock() vor dem finalen Entsperren aufrufen. */
|
||||
protected void beforePhysicalUnlock() {}
|
||||
/** TimeLock: lockControl.lock() nach dem Schließen der Hygiene-Öffnung aufrufen. */
|
||||
protected void afterHygieneClosing() {}
|
||||
|
||||
public BaseLockService(
|
||||
CommunityVerificationVoteRepository communityVerificationVoteRepository,
|
||||
CommunityVerificationRepository communityVerificationRepository,
|
||||
KeyholderVerificationRepository keyholderVerificationRepository,
|
||||
GameHistoryRepository gameHistoryRepository,
|
||||
UserRepository userRepository,
|
||||
KeyholderNotificationRepository keyholderNotificationRepository,
|
||||
SystemMessageService systemMessageService,
|
||||
UnlockCodeHistoryService unlockCodeHistoryService,
|
||||
KeyholderTaskChoiceRepository keyholderTaskChoiceRepository,
|
||||
CommunityTaskVoteRepository communityTaskVoteRepository) {
|
||||
this.communityVerificationVoteRepository = communityVerificationVoteRepository;
|
||||
this.communityVerificationRepository = communityVerificationRepository;
|
||||
this.keyholderVerificationRepository = keyholderVerificationRepository;
|
||||
this.gameHistoryRepository = gameHistoryRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.keyholderNotificationRepository = keyholderNotificationRepository;
|
||||
this.systemMessageService = systemMessageService;
|
||||
this.unlockCodeHistoryService = unlockCodeHistoryService;
|
||||
this.keyholderTaskChoiceRepository = keyholderTaskChoiceRepository;
|
||||
this.communityTaskVoteRepository = communityTaskVoteRepository;
|
||||
}
|
||||
|
||||
// ── Gemeinsame Hilfsmethoden ──────────────────────────────────────────────
|
||||
|
||||
protected Long calcOvertime() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
BaseLockEntity lock = getLock();
|
||||
if (lock.getTempOpeningTime() != null && lock.getTempOpeningDuration() != null) {
|
||||
LocalDateTime dueTime = lock.getTempOpeningTime().plusMinutes(lock.getTempOpeningDuration());
|
||||
if (now.isAfter(dueTime)) {
|
||||
return ChronoUnit.MINUTES.between(dueTime, now);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void reportKeyholder(Long overtime) {
|
||||
BaseLockEntity lock = getLock();
|
||||
KeyholderNotificationEntity notification = new KeyholderNotificationEntity();
|
||||
notification.setLockId(lock.getLockId());
|
||||
notification.setLockeeId(lock.getLockee());
|
||||
notification.setKeyholderUserId(lock.getKeyholder());
|
||||
notification.setViolationTime(LocalDateTime.now());
|
||||
notification.setOvertimeMinutes(overtime);
|
||||
keyholderNotificationRepository.save(notification);
|
||||
}
|
||||
|
||||
protected void sendMessage(UUID senderId, UUID receiverId, String text, String targetUrl,
|
||||
de.oaa.xxx.social.entity.MessageCause cause) {
|
||||
systemMessageService.send(senderId, receiverId, text, targetUrl, cause);
|
||||
}
|
||||
|
||||
// ── Aufgaben ──────────────────────────────────────────────────────────────
|
||||
|
||||
public void task(Task task) {
|
||||
BaseLockEntity lock = getLock();
|
||||
LOGGER.debug("Apply task {}", task);
|
||||
lock.setCurrentTask(task.getTitle());
|
||||
lock.setCurrentTaskDescription(task.getDescription());
|
||||
if (task.getMinutes() != null && task.getMinutes() > 0) {
|
||||
lock.setTaskUntil(LocalDateTime.now().plusMinutes(task.getMinutes()));
|
||||
}
|
||||
}
|
||||
|
||||
public String clearTask() {
|
||||
BaseLockEntity lock = getLock();
|
||||
LOGGER.debug("Clear task");
|
||||
lock.setCurrentTask(null);
|
||||
lock.setCurrentTaskDescription(null);
|
||||
lock.setTaskUntil(null);
|
||||
return "";
|
||||
}
|
||||
|
||||
protected void applyRandomTask() {
|
||||
LOGGER.debug("Apply random task");
|
||||
var tasks = getLock().getTasks();
|
||||
if (tasks != null && !tasks.isEmpty()) {
|
||||
task(tasks.get(new Random().nextInt(tasks.size())));
|
||||
}
|
||||
}
|
||||
|
||||
protected void startKeyholderVote() {
|
||||
BaseLockEntity lock = getLock();
|
||||
KeyholderTaskChoiceEntity choice = new KeyholderTaskChoiceEntity();
|
||||
choice.setLockId(lock.getLockId());
|
||||
choice.setCreatedAt(LocalDateTime.now());
|
||||
choice.setActive(true);
|
||||
choice.setExpiresAt(LocalDateTime.now().plusHours(1));
|
||||
keyholderTaskChoiceRepository.save(choice);
|
||||
userRepository.findById(lock.getKeyholder())
|
||||
.ifPresent(kh -> sendMessage(lock.getLockee(), kh.getUserId(),
|
||||
"Deine Lockee hat eine Aufgaben-Karte gezogen – wähle eine Aufgabe aus.",
|
||||
"/keyholder.html", de.oaa.xxx.social.entity.MessageCause.GAME_STATE));
|
||||
}
|
||||
|
||||
protected void startCommunityVote() {
|
||||
BaseLockEntity lock = getLock();
|
||||
CommunityTaskVoteEntity vote = new CommunityTaskVoteEntity();
|
||||
vote.setLockId(lock.getLockId());
|
||||
vote.setCreatedAt(LocalDateTime.now());
|
||||
vote.setExpiresAt(LocalDateTime.now().plusHours(1));
|
||||
vote.setActive(true);
|
||||
communityTaskVoteRepository.save(vote);
|
||||
}
|
||||
|
||||
// ── Temporäre Öffnung ─────────────────────────────────────────────────────
|
||||
|
||||
protected void startTempOpening(TempOpeningReason reason, Integer duration) {
|
||||
BaseLockEntity lock = getLock();
|
||||
assert duration != null;
|
||||
lock.setTempOpeningReason(reason);
|
||||
lock.setTempOpeningTime(LocalDateTime.now());
|
||||
lock.setTempOpeningDuration(duration);
|
||||
saveLock();
|
||||
unlockCodeHistoryService.save(lock.getLockee(), lock.getLockId(), lock.getName(), lock.getUnlockCode(), reason.toString());
|
||||
}
|
||||
|
||||
public String endHygieneOpening() {
|
||||
BaseLockEntity lock = getLock();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
Long overtime = calcOvertime();
|
||||
if (overtime != null) {
|
||||
if (lock.getKeyholder() != null) {
|
||||
reportKeyholder(overtime);
|
||||
}
|
||||
applyHygieneOvertime(overtime);
|
||||
}
|
||||
afterHygieneClosing();
|
||||
lock.setLastHygineOpening(now);
|
||||
lock.setTempOpeningDuration(null);
|
||||
lock.setTempOpeningTime(null);
|
||||
String code = CodeCreator.createNumeric(lock.getUnlockCodeLength());
|
||||
lock.setUnlockCode(code);
|
||||
saveLock();
|
||||
return code;
|
||||
}
|
||||
|
||||
// ── Entsperren ────────────────────────────────────────────────────────────
|
||||
|
||||
public void unlock(String unlockCode) {
|
||||
BaseLockEntity lock = getLock();
|
||||
beforePhysicalUnlock();
|
||||
lock.setUnlockTime(LocalDateTime.now());
|
||||
|
||||
boolean valid = true;
|
||||
if (lock.isEmergencyAutoUnlocked()) {
|
||||
valid = false;
|
||||
LOGGER.debug("Lock invalid - Emergency Auto-Unlock (1h timer)");
|
||||
} else if (lock.isTestLock()) {
|
||||
valid = false;
|
||||
} else if (Duration.between(lock.getStartTime(), lock.getUnlockTime()).toHours() > 24) {
|
||||
Set<LocalDate> verifications;
|
||||
if (lock.getKeyholder() != null) {
|
||||
verifications = keyholderVerificationRepository.findByLockId(lock.getLockId()).stream()
|
||||
.filter(v -> v.isValid())
|
||||
.map(v -> v.getVerificationDate())
|
||||
.collect(Collectors.toSet());
|
||||
} else {
|
||||
verifications = communityVerificationRepository.findByLockId(lock.getLockId()).stream()
|
||||
.filter(v -> v.isValid())
|
||||
.map(v -> v.getVerificationDate())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
LocalDate current = lock.getStartTime().toLocalDate();
|
||||
LocalDate last = lock.getUnlockTime().toLocalDate().minusDays(1);
|
||||
while (!current.isAfter(last)) {
|
||||
if (!verifications.contains(current)) {
|
||||
valid = false;
|
||||
LOGGER.debug("Lock invalid - no daily verification on {}", current);
|
||||
break;
|
||||
}
|
||||
current = current.plusDays(1);
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.debug("Unlocked at {}", lock.getUnlockTime());
|
||||
saveLock();
|
||||
|
||||
if (valid) {
|
||||
long durationMinutes = Duration.between(lock.getStartTime(), lock.getUnlockTime()).toMinutes();
|
||||
GameHistoryEntity entry = new GameHistoryEntity();
|
||||
entry.setGameType(getGameType());
|
||||
entry.setGameName(lock.getName());
|
||||
entry.setStartTime(lock.getStartTime());
|
||||
entry.setEndTime(lock.getUnlockTime());
|
||||
entry.setDurationMinutes(durationMinutes);
|
||||
entry.addParticipant(lock.getLockee(), GameRole.LOCKEE);
|
||||
if (lock.getKeyholder() != null) {
|
||||
entry.addParticipant(lock.getKeyholder(), GameRole.KEYHOLDER);
|
||||
}
|
||||
gameHistoryRepository.save(entry);
|
||||
|
||||
int minutes = (int) durationMinutes;
|
||||
userRepository.findById(lock.getLockee()).ifPresent(u -> {
|
||||
u.setLockeeXp(u.getLockeeXp() + minutes);
|
||||
userRepository.save(u);
|
||||
});
|
||||
if (lock.getKeyholder() != null) {
|
||||
userRepository.findById(lock.getKeyholder()).ifPresent(u -> {
|
||||
u.setKeyholderXp(u.getKeyholderXp() + minutes);
|
||||
userRepository.save(u);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package de.oaa.xxx.games.chastity.common;
|
||||
|
||||
import de.oaa.xxx.games.chastity.cardlock.CardlockTemplateEntity;
|
||||
import de.oaa.xxx.games.chastity.timelock.TimeLockTemplateEntity;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/templates")
|
||||
public class BaseLockTemplateController {
|
||||
|
||||
private final BaseLockTemplateRepository templateRepository;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public BaseLockTemplateController(BaseLockTemplateRepository templateRepository,
|
||||
UserRepository userRepository) {
|
||||
this.templateRepository = templateRepository;
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<Map<String, Object>> list(
|
||||
@RequestParam(defaultValue = "0") int page,
|
||||
@RequestParam(defaultValue = "20") int size,
|
||||
Principal principal) {
|
||||
var meOpt = userRepository.findByEmail(principal.getName());
|
||||
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||
UUID myId = meOpt.get().getUserId();
|
||||
|
||||
var pageable = PageRequest.of(page, Math.min(size, 50), Sort.by("name"));
|
||||
var pageResult = templateRepository.findByOwner(myId, pageable);
|
||||
|
||||
var content = pageResult.getContent().stream().map(t -> {
|
||||
Map<String, Object> dto = new LinkedHashMap<>();
|
||||
dto.put("templateId", t.getTemplateId());
|
||||
dto.put("name", t.getName());
|
||||
dto.put("lockType", t instanceof CardlockTemplateEntity ? "CARDLOCK" : "TIMELOCK");
|
||||
dto.put("hygineOpeningEveryMinites", t.getHygineOpeningEveryMinites());
|
||||
dto.put("hygineOpeningDurationMinutes", t.getHygineOpeningDurationMinutes());
|
||||
dto.put("taskCount", t.getTasks() != null ? t.getTasks().size() : 0);
|
||||
dto.put("requiresVerification", t.isRequiresVerification());
|
||||
return dto;
|
||||
}).toList();
|
||||
|
||||
Map<String, Object> response = new LinkedHashMap<>();
|
||||
response.put("content", content);
|
||||
response.put("page", pageResult.getNumber());
|
||||
response.put("totalPages", pageResult.getTotalPages());
|
||||
response.put("last", pageResult.isLast());
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<Map<String, Object>> getById(@PathVariable UUID id, Principal principal) {
|
||||
var meOpt = userRepository.findByEmail(principal.getName());
|
||||
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||
UUID myId = meOpt.get().getUserId();
|
||||
|
||||
var opt = templateRepository.findById(id);
|
||||
if (opt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
var t = opt.get();
|
||||
if (!t.getOwner().equals(myId)) return ResponseEntity.status(403).build();
|
||||
|
||||
if (t instanceof CardlockTemplateEntity c) {
|
||||
return ResponseEntity.ok(toCardlockDto(c));
|
||||
} else if (t instanceof TimeLockTemplateEntity tl) {
|
||||
return ResponseEntity.ok(toTimelockDto(tl));
|
||||
}
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
private Map<String, Object> toCardlockDto(CardlockTemplateEntity t) {
|
||||
Map<String, Object> dto = new LinkedHashMap<>();
|
||||
dto.put("_type", "CARDLOCK");
|
||||
dto.put("templateId", t.getTemplateId());
|
||||
dto.put("name", t.getName());
|
||||
dto.put("cardCountsMin", t.getCardCountsMin() != null ? t.getCardCountsMin() : Map.of());
|
||||
dto.put("cardCountsMax", t.getCardCountsMax() != null ? t.getCardCountsMax() : Map.of());
|
||||
dto.put("pickEveryMinute", t.getPickEveryMinute());
|
||||
dto.put("accumulatePicks", t.isAccumulatePicks());
|
||||
dto.put("showRemainingCards", t.isShowRemainingCards());
|
||||
dto.put("hygineOpeningEveryMinites", t.getHygineOpeningEveryMinites());
|
||||
dto.put("hygineOpeningDurationMinutes", t.getHygineOpeningDurationMinutes());
|
||||
dto.put("tasks", t.getTasks() != null ? t.getTasks() : List.of());
|
||||
dto.put("requiresVerification", t.isRequiresVerification());
|
||||
dto.put("taskCardMode", t.getTaskCardMode());
|
||||
return dto;
|
||||
}
|
||||
|
||||
private Map<String, Object> toTimelockDto(TimeLockTemplateEntity t) {
|
||||
Map<String, Object> dto = new LinkedHashMap<>();
|
||||
dto.put("_type", "TIMELOCK");
|
||||
dto.put("templateId", t.getTemplateId());
|
||||
dto.put("name", t.getName());
|
||||
dto.put("minTimeInMinutes", t.getMinTimeInMinutes());
|
||||
dto.put("maxTimeInMinutes", t.getMaxTimeInMinutes());
|
||||
dto.put("endTimeVisible", t.isEndTimeVisible());
|
||||
dto.put("hygineOpeningEveryMinites", t.getHygineOpeningEveryMinites());
|
||||
dto.put("hygineOpeningDurationMinutes", t.getHygineOpeningDurationMinutes());
|
||||
dto.put("tasks", t.getTasks() != null ? t.getTasks() : List.of());
|
||||
dto.put("taskEveryMinutes", t.getTaskEveryMinutes());
|
||||
dto.put("minTasksPerDay", t.getMinTasksPerDay());
|
||||
dto.put("spinningWheelEntries", t.getSpinningWheelEntries() != null ? t.getSpinningWheelEntries() : List.of());
|
||||
dto.put("spinsEveryMinutes", t.getSpinsEveryMinutes());
|
||||
dto.put("minSpinsPerDay", t.getMinSpinsPerDay());
|
||||
dto.put("requiresVerification", t.isRequiresVerification());
|
||||
dto.put("taskMode", t.getTaskCardMode());
|
||||
dto.put("penaltyType", t.getPenaltyType());
|
||||
dto.put("penaltyValue", t.getPenaltyValue());
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package de.oaa.xxx.games.chastity.common;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||
import de.oaa.xxx.games.chastity.tasks.TaskListConverter;
|
||||
import de.oaa.xxx.games.chastity.tasks.TaskMode;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Convert;
|
||||
import jakarta.persistence.DiscriminatorColumn;
|
||||
import jakarta.persistence.DiscriminatorType;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Inheritance;
|
||||
import jakarta.persistence.InheritanceType;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "lock_template")
|
||||
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
|
||||
@DiscriminatorColumn(name = "lock_type", discriminatorType = DiscriminatorType.STRING)
|
||||
public class BaseLockTemplateEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
@Column
|
||||
private UUID templateId;
|
||||
@Column(nullable = false)
|
||||
private UUID owner;
|
||||
@Column
|
||||
private String name;
|
||||
@Column
|
||||
private Integer hygineOpeningDurationMinutes;
|
||||
@Column
|
||||
private Integer hygineOpeningEveryMinites;
|
||||
@Convert(converter = TaskListConverter.class)
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private List<Task> tasks;
|
||||
@Column
|
||||
private boolean requiresVerification;
|
||||
@Column(nullable = false)
|
||||
private TaskMode taskMode = TaskMode.RANDOM;
|
||||
|
||||
public TaskMode getTaskCardMode() {
|
||||
return taskMode != null ? taskMode : TaskMode.RANDOM;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package de.oaa.xxx.games.chastity.common;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface BaseLockTemplateRepository extends JpaRepository<BaseLockTemplateEntity, UUID>{
|
||||
List<BaseLockTemplateEntity> findByOwner(UUID owner);
|
||||
Page<BaseLockTemplateEntity> findByOwner(UUID owner, Pageable pageable);
|
||||
}
|
||||
@@ -11,7 +11,7 @@ public class CodeCreator {
|
||||
return create(digits, CHARS_N);
|
||||
}
|
||||
|
||||
public static String createAlphanumericCode(int digits) {
|
||||
public static String createAlphanumeric(int digits) {
|
||||
return create(digits, CHARS_AN);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.oaa.xxx.games.chastity.common;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface Verification {
|
||||
|
||||
LocalDate getVerificationDate();
|
||||
|
||||
boolean isValid();
|
||||
|
||||
VerificationCommonDTO toCommonVerification();
|
||||
|
||||
String getCode();
|
||||
|
||||
void setCode(String code);
|
||||
|
||||
LocalDateTime getCreatedAt();
|
||||
|
||||
void setCreatedAt(LocalDateTime createdAt);
|
||||
|
||||
UUID getKeyholderId();
|
||||
|
||||
void setKeyholderId(UUID id);
|
||||
|
||||
UUID getLockeeId();
|
||||
|
||||
void setLockeeId(UUID id);
|
||||
|
||||
UUID getLockId();
|
||||
|
||||
void setLockId(UUID id);
|
||||
|
||||
UUID getId();
|
||||
|
||||
void setId(UUID id);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.oaa.xxx.games.chastity.common;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public record VerificationCommonDTO(UUID verficationId, UUID lockId, String code,
|
||||
LocalDateTime createdAt, byte[] image, UUID lockeeId, UUID keyholderID) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import de.oaa.xxx.user.UserEntity;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/games/chastity/community")
|
||||
public class BaseCommunityDisplayController {
|
||||
|
||||
private final BaseCommunityDisplayRepository baseCommunityDisplayRepository;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public BaseCommunityDisplayController(BaseCommunityDisplayRepository baseCommunityDisplayRepository,
|
||||
UserRepository userRepository) {
|
||||
this.baseCommunityDisplayRepository = baseCommunityDisplayRepository;
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<Page<BaseCommunityDisplayDTO>> getAllDisplays(
|
||||
@PageableDefault(size = 10, sort = "createdAt", direction = Direction.DESC) Pageable pageable) {
|
||||
Page<BaseCommunityDisplayEntity> page = baseCommunityDisplayRepository.findAll(pageable);
|
||||
|
||||
Set<UUID> lockeeIds = page.getContent().stream()
|
||||
.map(BaseCommunityDisplayEntity::getLockeeId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Map<UUID, String> nameMap = userRepository.findAllById(lockeeIds).stream()
|
||||
.collect(Collectors.toMap(UserEntity::getUserId, UserEntity::getName));
|
||||
|
||||
Page<BaseCommunityDisplayDTO> result = page.map(e ->
|
||||
e.toBaseCommunityDisplay(nameMap.getOrDefault(e.getLockeeId(), "")));
|
||||
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public record BaseCommunityDisplayDTO(UUID displayId, UUID lockId, LocalDateTime createdAt, UUID lockeeId,
|
||||
UUID keyholderId, String type, String lockeeName) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.DiscriminatorColumn;
|
||||
import jakarta.persistence.DiscriminatorType;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Inheritance;
|
||||
import jakarta.persistence.InheritanceType;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "community_display")
|
||||
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
|
||||
@DiscriminatorColumn(name = "display_type", discriminatorType = DiscriminatorType.STRING)
|
||||
public abstract class BaseCommunityDisplayEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
@Column
|
||||
private UUID displayId;
|
||||
@Column(nullable = false)
|
||||
private UUID lockId;
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
@Column(nullable = false)
|
||||
private UUID lockeeId;
|
||||
@Column
|
||||
private UUID keyholderId;
|
||||
|
||||
public abstract String getType();
|
||||
|
||||
public BaseCommunityDisplayDTO toBaseCommunityDisplay(String lockeeName) {
|
||||
return new BaseCommunityDisplayDTO(displayId, lockId, createdAt, lockeeId, keyholderId, getType(), lockeeName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface BaseCommunityDisplayRepository extends JpaRepository<BaseCommunityDisplayEntity, UUID> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/games/chastity/community/pillory")
|
||||
public class CommunityPilloryController {
|
||||
|
||||
private CommunityPilloryRepository repo;
|
||||
|
||||
public CommunityPilloryController(CommunityPilloryRepository repo) {
|
||||
this.repo = repo;
|
||||
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<CommunityPilloryDTO> getPillory(@PathVariable UUID id) {
|
||||
var pillory = repo.findById(id);
|
||||
if (pillory.isPresent()) {
|
||||
return ResponseEntity.ok(pillory.get().toPillory());
|
||||
}
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public record CommunityPilloryDTO(UUID displayId, UUID lockId, LocalDateTime createdAt, UUID lockeeId,
|
||||
UUID keyholderId, CommunityPilloryReason reason, String message) {
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.DiscriminatorValue;
|
||||
import jakarta.persistence.Entity;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@DiscriminatorValue("PILLORY")
|
||||
public class CommunityPilloryEntity extends BaseCommunityDisplayEntity {
|
||||
|
||||
@Column
|
||||
private CommunityPilloryReason reason;
|
||||
@Column
|
||||
private String message;
|
||||
|
||||
@Override
|
||||
public String getType() { return "PILLORY"; }
|
||||
|
||||
public CommunityPilloryDTO toPillory() {
|
||||
return new CommunityPilloryDTO(getDisplayId(), getLockId(), getCreatedAt(), getLockeeId(), getKeyholderId(),
|
||||
reason, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
public enum CommunityPilloryReason {
|
||||
|
||||
HYGIENE_OPENING_EXEEDED,
|
||||
KEYHOLDER_DESCESSION;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface CommunityPilloryRepository extends JpaRepository<CommunityPilloryEntity, UUID> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import de.oaa.xxx.games.chastity.common.BaseLockRepository;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/games/chastity/community/taskvote")
|
||||
public class CommunityTaskVoteController {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final CommunityTaskVoteRepository taskVoteRepository;
|
||||
private final CommunityTaskVoteEntryRepository taskVoteEntryRepository;
|
||||
private final BaseLockRepository baseLockRepository;
|
||||
|
||||
public CommunityTaskVoteController(UserRepository userRepository,
|
||||
CommunityTaskVoteRepository taskVoteRepository,
|
||||
CommunityTaskVoteEntryRepository taskVoteEntryRepository,
|
||||
BaseLockRepository baseLockRepository) {
|
||||
this.userRepository = userRepository;
|
||||
this.taskVoteRepository = taskVoteRepository;
|
||||
this.taskVoteEntryRepository = taskVoteEntryRepository;
|
||||
this.baseLockRepository = baseLockRepository;
|
||||
}
|
||||
|
||||
@GetMapping("/{displayId}")
|
||||
public ResponseEntity<CommunityTaskVoteDisplayDTO> getVote(@PathVariable UUID displayId, Principal principal) {
|
||||
var meOpt = userRepository.findByEmail(principal.getName());
|
||||
if (meOpt.isEmpty())
|
||||
return ResponseEntity.status(401).build();
|
||||
UUID myId = meOpt.get().getUserId();
|
||||
|
||||
var voteOpt = taskVoteRepository.findById(displayId);
|
||||
if (voteOpt.isEmpty() || !voteOpt.get().isActive()) {
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
var vote = voteOpt.get();
|
||||
var lockOpt = baseLockRepository.findById(vote.getLockId());
|
||||
if (lockOpt.isEmpty()) {
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
var lock = lockOpt.get();
|
||||
boolean isOwnLock = lock.getLockee().equals(myId);
|
||||
var entryList = new ArrayList<CommunityTaskVoteDisplayEntryDTO>();
|
||||
var tasks = lock.getTasks();
|
||||
var userVote = taskVoteEntryRepository.findByDisplayIdAndUserId(displayId, myId);
|
||||
for (int i = 0; i < tasks.size(); i++) {
|
||||
var task = tasks.get(i);
|
||||
boolean myVote = false;
|
||||
int votes = taskVoteEntryRepository.countByDisplayIdAndTaskIndex(displayId, i);
|
||||
if (userVote != null && userVote.getTaskIndex() == i) {
|
||||
myVote = true;
|
||||
}
|
||||
entryList.add(new CommunityTaskVoteDisplayEntryDTO(task.getTitle(),
|
||||
task.getDescription(), task.getMinutes(), votes, myVote));
|
||||
}
|
||||
return ResponseEntity.ok(new CommunityTaskVoteDisplayDTO(displayId, voteOpt.get().getCreatedAt(), displayId, myId, isOwnLock, entryList));
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/{displayId}/vote/{taskIndex}")
|
||||
@Transactional
|
||||
public ResponseEntity<Void> castVote(@PathVariable UUID displayId, @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 = taskVoteRepository.findById(displayId);
|
||||
if (voteOpt.isEmpty())
|
||||
return ResponseEntity.notFound().build();
|
||||
var vote = voteOpt.get();
|
||||
if (!vote.isActive() || vote.getExpiresAt().isBefore(LocalDateTime.now()))
|
||||
return ResponseEntity.status(409).build();
|
||||
|
||||
var lockOpt = baseLockRepository.findById(vote.getLockId());
|
||||
if (lockOpt.isEmpty())
|
||||
return ResponseEntity.notFound().build();
|
||||
var lock = lockOpt.get();
|
||||
|
||||
if (lock.getLockee().equals(myId))
|
||||
return ResponseEntity.status(403).build();
|
||||
|
||||
if (lock.getTasks() == null || taskIndex < 0 || taskIndex >= lock.getTasks().size())
|
||||
return ResponseEntity.badRequest().build();
|
||||
|
||||
if (taskVoteEntryRepository.existsByDisplayIdAndUserId(displayId, myId))
|
||||
return ResponseEntity.status(409).build();
|
||||
|
||||
CommunityTaskVoteEntryEntity entry = new CommunityTaskVoteEntryEntity();
|
||||
entry.setDisplayId(displayId);
|
||||
entry.setUserId(myId);
|
||||
entry.setTaskIndex(taskIndex);
|
||||
taskVoteEntryRepository.save(entry);
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public record CommunityTaskVoteDTO(UUID displayId, UUID lockId, LocalDateTime createdAt, UUID lockeeId,
|
||||
UUID keyholderId, boolean active, LocalDateTime expiresAt, int winningTaskIndex, List<CommunityTaskVoteEntryDTO> entries) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public record CommunityTaskVoteDisplayDTO(UUID displayId, LocalDateTime createdAt, UUID lockeeId, UUID keyholderId,
|
||||
boolean isOwnLock, List<CommunityTaskVoteDisplayEntryDTO> entries) {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
public record CommunityTaskVoteDisplayEntryDTO(String title, String description, Integer minutes, Integer votes,
|
||||
boolean ownVote) {
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.DiscriminatorValue;
|
||||
import jakarta.persistence.Entity;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@DiscriminatorValue("TASK_VOTE")
|
||||
public class CommunityTaskVoteEntity extends BaseCommunityDisplayEntity {
|
||||
|
||||
@Column
|
||||
private boolean active;
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime expiresAt;
|
||||
/** null until completed */
|
||||
@Column
|
||||
private Integer winningTaskIndex;
|
||||
|
||||
@Override
|
||||
public String getType() { return "TASK_VOTE"; }
|
||||
|
||||
public CommunityTaskVoteDTO toCommunityTaskVote(List<CommunityTaskVoteEntryDTO> entries) {
|
||||
return new CommunityTaskVoteDTO(getDisplayId(), getLockId(), expiresAt, getLockeeId(), getKeyholderId(), active,
|
||||
expiresAt, 0, entries);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record CommunityTaskVoteEntryDTO(UUID entryId, UUID displayId, UUID userId, int taskIndex) {
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.oaa.xxx.games.chastity.vote;
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
@@ -18,11 +18,15 @@ public class CommunityTaskVoteEntryEntity {
|
||||
private UUID entryId;
|
||||
|
||||
@Column(nullable = false)
|
||||
private UUID voteSessionId;
|
||||
private UUID displayId;
|
||||
|
||||
@Column(nullable = false)
|
||||
private UUID voterUserId;
|
||||
private UUID userId;
|
||||
|
||||
@Column(nullable = false)
|
||||
private int taskIndex;
|
||||
|
||||
public CommunityTaskVoteEntryDTO toCommunityTaskVoteEntry() {
|
||||
return new CommunityTaskVoteEntryDTO(entryId, displayId, userId, taskIndex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface CommunityTaskVoteEntryRepository extends JpaRepository<CommunityTaskVoteEntryEntity, UUID> {
|
||||
List<CommunityTaskVoteEntryEntity> findByDisplayId(UUID voteSessionId);
|
||||
boolean existsByDisplayIdAndUserId(UUID displayId, UUID userId);
|
||||
CommunityTaskVoteEntryEntity findByDisplayIdAndUserId(UUID displayId, UUID userId);
|
||||
Integer countByDisplayIdAndTaskIndex(UUID displayId, int taskIndex);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
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> findByActiveTrue();
|
||||
List<CommunityTaskVoteEntity> findByActiveTrueAndExpiresAtBefore(LocalDateTime time);
|
||||
boolean existsByLockIdAndActiveTrue(UUID lockId);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.oaa.xxx.games.chastity.cardlock;
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
@@ -13,25 +13,24 @@ import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import de.oaa.xxx.games.chastity.cardlock.CardlockRepository;
|
||||
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.vote.CommunityTaskVoteEntryRepository;
|
||||
import de.oaa.xxx.games.chastity.vote.CommunityTaskVoteRepository;
|
||||
import de.oaa.xxx.social.SystemMessageService;
|
||||
|
||||
@Component
|
||||
public class TaskVoteScheduler {
|
||||
public class CommunityTaskVoteScheduler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TaskVoteScheduler.class);
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CommunityTaskVoteScheduler.class);
|
||||
|
||||
private final CommunityTaskVoteRepository communityTaskVoteRepository;
|
||||
private final CommunityTaskVoteEntryRepository communityTaskVoteEntryRepository;
|
||||
private final CardlockRepository cardlockRepository;
|
||||
private final AssignedTaskRepository assignedTaskRepository;
|
||||
private final SystemMessageService systemMessageService;
|
||||
private SystemMessageService systemMessageService;
|
||||
|
||||
public TaskVoteScheduler(CommunityTaskVoteRepository communityTaskVoteRepository,
|
||||
public CommunityTaskVoteScheduler(CommunityTaskVoteRepository communityTaskVoteRepository,
|
||||
CommunityTaskVoteEntryRepository communityTaskVoteEntryRepository,
|
||||
CardlockRepository cardlockRepository,
|
||||
AssignedTaskRepository assignedTaskRepository,
|
||||
@@ -47,27 +46,26 @@ public class TaskVoteScheduler {
|
||||
@Transactional
|
||||
public void processExpiredVotes() {
|
||||
var expired = communityTaskVoteRepository
|
||||
.findByStatusAndExpiresAtBefore("ACTIVE", LocalDateTime.now());
|
||||
.findByActiveTrueAndExpiresAtBefore(LocalDateTime.now());
|
||||
|
||||
for (var vote : expired) {
|
||||
LOG.debug("Processing expired community task vote {}", vote.getVoteSessionId());
|
||||
LOG.debug("Processing expired community task vote {}", vote.getDisplayId());
|
||||
|
||||
var lockOpt = cardlockRepository.findById(vote.getLockId());
|
||||
if (lockOpt.isEmpty()) {
|
||||
vote.setStatus("COMPLETED");
|
||||
vote.setActive(false);
|
||||
communityTaskVoteRepository.save(vote);
|
||||
continue;
|
||||
}
|
||||
var lock = lockOpt.get();
|
||||
List<Task> tasks = lock.getTasks();
|
||||
if (tasks == null || tasks.isEmpty()) {
|
||||
vote.setStatus("COMPLETED");
|
||||
vote.setActive(false);
|
||||
communityTaskVoteRepository.save(vote);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Stimmen auszählen
|
||||
var entries = communityTaskVoteEntryRepository.findByVoteSessionId(vote.getVoteSessionId());
|
||||
var entries = communityTaskVoteEntryRepository.findByDisplayId(vote.getDisplayId());
|
||||
int winnerIndex;
|
||||
if (entries.isEmpty()) {
|
||||
winnerIndex = new Random().nextInt(tasks.size());
|
||||
@@ -80,7 +78,6 @@ public class TaskVoteScheduler {
|
||||
}
|
||||
}
|
||||
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);
|
||||
@@ -92,22 +89,21 @@ public class TaskVoteScheduler {
|
||||
Task task = tasks.get(winnerIndex);
|
||||
AssignedTaskEntity assigned = new AssignedTaskEntity();
|
||||
assigned.setLockId(lock.getLockId());
|
||||
assigned.setTaskTitle(task.resolveTitle());
|
||||
assigned.setTaskTitle(task.getTitle());
|
||||
assigned.setTaskDescription(task.getDescription());
|
||||
assigned.setTaskText(task.resolveTitle());
|
||||
assigned.setTaskText(task.getTitle());
|
||||
assigned.setTaskMinutes(task.getMinutes());
|
||||
assigned.setAssignedAt(LocalDateTime.now());
|
||||
assigned.setAcceptDeadline(LocalDateTime.now().plusHours(1));
|
||||
assigned.setStatus("PENDING");
|
||||
assignedTaskRepository.save(assigned);
|
||||
|
||||
vote.setStatus("COMPLETED");
|
||||
vote.setActive(false);
|
||||
vote.setWinningTaskIndex(winnerIndex);
|
||||
communityTaskVoteRepository.save(vote);
|
||||
|
||||
// Lockee benachrichtigen
|
||||
sendMessage(lock.getLockee(),
|
||||
"Die Community hat für deine Aufgabe abgestimmt: \"" + task.resolveTitle() + "\"",
|
||||
"Die Community hat für deine Aufgabe abgestimmt: \"" + task.getTitle() + "\"",
|
||||
"/activelock.html?lockId=" + lock.getLockId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import de.oaa.xxx.games.chastity.common.CodeCreator;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/games/chastity/community/verification")
|
||||
@Transactional
|
||||
public class CommunityVerificationController {
|
||||
|
||||
private final CommunityVerificationRepository verificationRepository;
|
||||
private final CommunityVerificationVoteRepository verificationVoteRepository;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public CommunityVerificationController(CommunityVerificationRepository verificationRepository,
|
||||
CommunityVerificationVoteRepository verificationVoteRepository, UserRepository userRepository) {
|
||||
this.verificationRepository = verificationRepository;
|
||||
this.verificationVoteRepository = verificationVoteRepository;
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@GetMapping("/{displayId}")
|
||||
public ResponseEntity<CommunityVerificationDTO> get(@PathVariable UUID displayId, Principal principal) {
|
||||
var optional = verificationRepository.findById(displayId);
|
||||
if (optional.isEmpty()) {
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
var entity = optional.get();
|
||||
|
||||
boolean isOwnLock = false;
|
||||
Boolean myVote = null;
|
||||
if (principal != null) {
|
||||
var userOpt = userRepository.findByEmail(principal.getName());
|
||||
if (userOpt.isPresent()) {
|
||||
var myId = userOpt.get().getUserId();
|
||||
isOwnLock = myId.equals(entity.getLockeeId());
|
||||
if (!isOwnLock) {
|
||||
myVote = verificationVoteRepository
|
||||
.findByVerificationIdAndUserId(displayId, myId)
|
||||
.map(CommunityVerificationVoteEntity::isUpvote)
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dto = entity.toVerification(isOwnLock, myVote);
|
||||
verificationVoteRepository.findAllByVerificationId(displayId).stream()
|
||||
.map(CommunityVerificationVoteEntity::toVerificationVote)
|
||||
.forEach(dto.votes()::add);
|
||||
return ResponseEntity.ok(dto);
|
||||
}
|
||||
|
||||
@GetMapping("/new")
|
||||
public ResponseEntity<CommunityVerificationDTO> createVerification() {
|
||||
var verification = new CommunityVerificationEntity();
|
||||
verification.setDisplayId(UUID.randomUUID());
|
||||
verification.setCode(CodeCreator.createAlphanumeric(6));
|
||||
verification.setCreatedAt(LocalDateTime.now());
|
||||
verificationRepository.save(verification);
|
||||
return ResponseEntity.ok(verification.toVerification(true, null));
|
||||
}
|
||||
|
||||
@PutMapping("/{displayId}")
|
||||
public ResponseEntity<Void> update(@PathVariable UUID displayId, @RequestBody CommunityVerificationDTO dto,
|
||||
Principal principal) {
|
||||
var user = userRepository.findByEmail(principal.getName()).orElse(null);
|
||||
if (user == null) {
|
||||
return ResponseEntity.status(401).build();
|
||||
}
|
||||
var entity = verificationRepository.findById(displayId).orElse(null);
|
||||
if (entity == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
if (entity.getCreatedAt().isBefore(LocalDateTime.now().minusHours(1))) {
|
||||
return ResponseEntity.status(HttpStatus.GONE).build();
|
||||
}
|
||||
if (dto.image() != null) {
|
||||
entity.setImage(dto.image());
|
||||
}
|
||||
verificationRepository.save(entity);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping("/{verificationId}/vote/")
|
||||
public ResponseEntity<Void> addVote(@PathVariable UUID verificationId, @RequestBody CommunityVerificationVoteDTO dto,
|
||||
Principal principal) {
|
||||
var user = userRepository.findByEmail(principal.getName()).orElse(null);
|
||||
if (user == null) return ResponseEntity.status(401).build();
|
||||
if (!verificationRepository.existsById(verificationId)) return ResponseEntity.notFound().build();
|
||||
|
||||
var vEntity = verificationRepository.findById(verificationId).orElse(null);
|
||||
if (vEntity == null) return ResponseEntity.notFound().build();
|
||||
if (user.getUserId().equals(vEntity.getLockeeId())) return ResponseEntity.status(403).build();
|
||||
if (verificationVoteRepository.findByVerificationIdAndUserId(verificationId, user.getUserId()).isPresent()) {
|
||||
return ResponseEntity.status(409).build();
|
||||
}
|
||||
var vote = new CommunityVerificationVoteEntity();
|
||||
vote.setVoteId(UUID.randomUUID());
|
||||
vote.setVerificationId(verificationId);
|
||||
vote.setUserId(user.getUserId());
|
||||
vote.setUpvote(dto.upvote());
|
||||
if (dto.upvote()) {
|
||||
vEntity.setCountUpvotes(vEntity.getCountUpvotes() + 1);
|
||||
} else {
|
||||
vEntity.setCountDownvotes(vEntity.getCountDownvotes() + 1);
|
||||
}
|
||||
verificationVoteRepository.save(vote);
|
||||
return ResponseEntity.accepted().build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public record CommunityVerificationDTO(
|
||||
UUID displayId, String code, LocalDateTime verificationTime,
|
||||
byte[] image, List<CommunityVerificationVoteDTO> votes, int upvotes, int downvotes,
|
||||
boolean isOwnLock, Boolean myVote) {}
|
||||
@@ -0,0 +1,62 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.oaa.xxx.games.chastity.common.Verification;
|
||||
import de.oaa.xxx.games.chastity.common.VerificationCommonDTO;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.DiscriminatorValue;
|
||||
import jakarta.persistence.Entity;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@DiscriminatorValue("VERIFICATION")
|
||||
public class CommunityVerificationEntity extends BaseCommunityDisplayEntity implements Verification {
|
||||
|
||||
@Column(nullable = false)
|
||||
private String code;
|
||||
@Column(columnDefinition = "MEDIUMBLOB")
|
||||
private byte[] image;
|
||||
@Column
|
||||
private int countUpvotes;
|
||||
@Column
|
||||
private int countDownvotes;
|
||||
|
||||
@Override
|
||||
public String getType() { return "VERIFICATION"; }
|
||||
|
||||
public CommunityVerificationDTO toVerification(boolean isOwnLock, Boolean myVote) {
|
||||
return new CommunityVerificationDTO(getDisplayId(), code, getCreatedAt(), image, new ArrayList<>(), countUpvotes, countDownvotes, isOwnLock, myVote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate getVerificationDate() {
|
||||
return getCreatedAt().toLocalDate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return countUpvotes > countDownvotes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VerificationCommonDTO toCommonVerification() {
|
||||
return new VerificationCommonDTO(getDisplayId(), getLockId(), code, getCreatedAt(), image, getLockeeId(),
|
||||
getKeyholderId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getId() {
|
||||
return getDisplayId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(UUID id) {
|
||||
setDisplayId(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface CommunityVerificationRepository extends JpaRepository<CommunityVerificationEntity, UUID> {
|
||||
|
||||
org.springframework.data.domain.Page<CommunityVerificationEntity> findAllByImageIsNotNull(Pageable pageable);
|
||||
|
||||
java.util.List<CommunityVerificationEntity> findByLockId(UUID lockId);
|
||||
|
||||
java.util.List<CommunityVerificationEntity> findByLockIdAndCreatedAtBetweenAndImageIsNotNull(UUID lockId, java.time.LocalDateTime from, java.time.LocalDateTime to);
|
||||
|
||||
java.util.List<CommunityVerificationEntity> findByLockIdAndCreatedAtBetweenAndImageIsNull(UUID lockId, java.time.LocalDateTime from, java.time.LocalDateTime to);
|
||||
|
||||
org.springframework.data.domain.Page<CommunityVerificationEntity> findByKeyholderIdIsNullAndCreatedAtBetweenAndImageIsNotNull(
|
||||
java.time.LocalDateTime from, java.time.LocalDateTime to, org.springframework.data.domain.Pageable pageable);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record CommunityVerificationVoteDTO (UUID voteId, UUID userId, boolean upvote) {}
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.oaa.xxx.games.chastity.verification;
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -13,7 +13,7 @@ import lombok.Setter;
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name="verification_vote")
|
||||
public class VerificationVoteEntity {
|
||||
public class CommunityVerificationVoteEntity {
|
||||
|
||||
@Id
|
||||
@Column
|
||||
@@ -25,7 +25,7 @@ public class VerificationVoteEntity {
|
||||
@Column(nullable = false)
|
||||
private boolean upvote;
|
||||
|
||||
public VerificationVoteDTO toVerificationVote() {
|
||||
return new VerificationVoteDTO(voteId, userId, upvote);
|
||||
public CommunityVerificationVoteDTO toVerificationVote() {
|
||||
return new CommunityVerificationVoteDTO(voteId, userId, upvote);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.oaa.xxx.games.chastity.community;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface CommunityVerificationVoteRepository extends JpaRepository<CommunityVerificationVoteEntity, UUID> {
|
||||
|
||||
List<CommunityVerificationVoteEntity> findAllByVerificationId(UUID verificationId);
|
||||
|
||||
java.util.Optional<CommunityVerificationVoteEntity> findByVerificationIdAndUserId(UUID verificationId, UUID userId);
|
||||
|
||||
void deleteAllByVerificationId(UUID verificationId);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,53 +1,52 @@
|
||||
package de.oaa.xxx.games.chastity.cardlock;
|
||||
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderTaskChoiceRepository;
|
||||
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.vote.CommunityTaskVoteEntryEntity;
|
||||
import de.oaa.xxx.games.chastity.vote.CommunityTaskVoteEntryRepository;
|
||||
import de.oaa.xxx.games.chastity.vote.CommunityTaskVoteRepository;
|
||||
import de.oaa.xxx.social.SystemMessageService;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
package de.oaa.xxx.games.chastity.keyholder;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import de.oaa.xxx.games.chastity.cardlock.CardlockRepository;
|
||||
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.SystemMessageService;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/task-card")
|
||||
public class TaskCardController {
|
||||
@RequestMapping("/games/chastity/keyholder/choices")
|
||||
public class KeyholderTaskChoiceController {
|
||||
|
||||
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 SystemMessageService systemMessageService;
|
||||
|
||||
public TaskCardController(CardlockRepository cardlockRepository,
|
||||
public KeyholderTaskChoiceController(CardlockRepository cardlockRepository,
|
||||
UserRepository userRepository,
|
||||
KeyholderTaskChoiceRepository keyholderTaskChoiceRepository,
|
||||
CommunityTaskVoteRepository communityTaskVoteRepository,
|
||||
CommunityTaskVoteEntryRepository communityTaskVoteEntryRepository,
|
||||
AssignedTaskRepository assignedTaskRepository,
|
||||
SystemMessageService systemMessageService) {
|
||||
this.cardlockRepository = cardlockRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.keyholderTaskChoiceRepository = keyholderTaskChoiceRepository;
|
||||
this.communityTaskVoteRepository = communityTaskVoteRepository;
|
||||
this.communityTaskVoteEntryRepository = communityTaskVoteEntryRepository;
|
||||
this.assignedTaskRepository = assignedTaskRepository;
|
||||
this.systemMessageService = systemMessageService;
|
||||
}
|
||||
|
||||
// ── Keyholder: ausstehende Aufgaben-Karten-Entscheidungen ─────────────────
|
||||
|
||||
@GetMapping("/keyholder/choices")
|
||||
@GetMapping
|
||||
@Transactional(readOnly = true)
|
||||
public ResponseEntity<List<Map<String, Object>>> getPendingChoices(Principal principal) {
|
||||
var meOpt = userRepository.findByEmail(principal.getName());
|
||||
@@ -59,7 +58,7 @@ public class TaskCardController {
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
|
||||
for (var lock : locks) {
|
||||
var pending = keyholderTaskChoiceRepository.findByLockIdAndStatus(lock.getLockId(), "PENDING");
|
||||
var pending = keyholderTaskChoiceRepository.findByLockIdAndActiveTrue(lock.getLockId());
|
||||
if (pending.isEmpty()) continue;
|
||||
|
||||
var lockee = userRepository.findById(lock.getLockee()).orElse(null);
|
||||
@@ -81,7 +80,7 @@ public class TaskCardController {
|
||||
|
||||
record PenaltyRequest(Integer penaltyFreezeMinutes, Integer penaltyRedCards) {}
|
||||
|
||||
@PostMapping("/keyholder/choices/{choiceId}/choose/{taskIndex}")
|
||||
@PostMapping("/{choiceId}/choose/{taskIndex}")
|
||||
@Transactional
|
||||
public ResponseEntity<Void> chooseTask(@PathVariable UUID choiceId,
|
||||
@PathVariable int taskIndex,
|
||||
@@ -100,7 +99,7 @@ public class TaskCardController {
|
||||
var lock = lockOpt.get();
|
||||
|
||||
if (!myId.equals(lock.getKeyholder())) return ResponseEntity.status(403).build();
|
||||
if (!"PENDING".equals(choice.getStatus())) return ResponseEntity.status(409).build();
|
||||
if (!choice.isActive()) return ResponseEntity.status(409).build();
|
||||
|
||||
List<Task> tasks = lock.getTasks();
|
||||
if (tasks == null || taskIndex < 0 || taskIndex >= tasks.size())
|
||||
@@ -109,9 +108,9 @@ public class TaskCardController {
|
||||
Task task = tasks.get(taskIndex);
|
||||
AssignedTaskEntity assigned = new AssignedTaskEntity();
|
||||
assigned.setLockId(lock.getLockId());
|
||||
assigned.setTaskTitle(task.resolveTitle());
|
||||
assigned.setTaskTitle(task.getTitle());
|
||||
assigned.setTaskDescription(task.getDescription());
|
||||
assigned.setTaskText(task.resolveTitle());
|
||||
assigned.setTaskText(task.getTitle());
|
||||
assigned.setTaskMinutes(task.getMinutes());
|
||||
assigned.setAssignedAt(LocalDateTime.now());
|
||||
assigned.setAcceptDeadline(LocalDateTime.now().plusHours(1));
|
||||
@@ -122,7 +121,7 @@ public class TaskCardController {
|
||||
}
|
||||
assignedTaskRepository.save(assigned);
|
||||
|
||||
choice.setStatus("CHOSEN");
|
||||
choice.setActive(false);
|
||||
keyholderTaskChoiceRepository.save(choice);
|
||||
|
||||
sendMessage(myId, lock.getLockee(),
|
||||
@@ -132,94 +131,6 @@ public class TaskCardController {
|
||||
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);
|
||||
|
||||
boolean isOwnLock = lock.getLockee().equals(myId);
|
||||
|
||||
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("createdAt", vote.getCreatedAt().toString());
|
||||
m.put("expiresAt", vote.getExpiresAt().toString());
|
||||
m.put("tasks", taskList);
|
||||
m.put("voteCounts", voteCounts);
|
||||
m.put("myVote", isOwnLock ? "own" : myVote);
|
||||
m.put("isOwnLock", isOwnLock);
|
||||
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.getLockee().equals(myId)) return ResponseEntity.status(403).build();
|
||||
|
||||
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 ───────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -230,7 +141,7 @@ public class TaskCardController {
|
||||
Task t = tasks.get(i);
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("index", i);
|
||||
m.put("title", t.resolveTitle());
|
||||
m.put("title", t.getTitle());
|
||||
m.put("description", t.getDescription() != null ? t.getDescription() : "");
|
||||
m.put("minutes", t.getMinutes() != null ? t.getMinutes() : 0);
|
||||
list.add(m);
|
||||
@@ -20,10 +20,12 @@ public class KeyholderTaskChoiceEntity {
|
||||
@Column(nullable = false)
|
||||
private UUID lockId;
|
||||
|
||||
/** PENDING | CHOSEN */
|
||||
@Column(nullable = false)
|
||||
private String status = "PENDING";
|
||||
private boolean active = true;
|
||||
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column
|
||||
private LocalDateTime expiresAt;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package de.oaa.xxx.games.chastity.keyholder;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface KeyholderTaskChoiceRepository extends JpaRepository<KeyholderTaskChoiceEntity, UUID> {
|
||||
List<KeyholderTaskChoiceEntity> findByLockIdAndStatus(UUID lockId, String status);
|
||||
List<KeyholderTaskChoiceEntity> findByLockIdAndActiveTrue(UUID lockId);
|
||||
List<KeyholderTaskChoiceEntity> findByActiveTrueAndExpiresAtBefore(LocalDateTime time);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package de.oaa.xxx.games.chastity.keyholder;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
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.cardlock.CardlockRepository;
|
||||
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.SystemMessageService;
|
||||
import de.oaa.xxx.social.entity.MessageCause;
|
||||
|
||||
@Component
|
||||
public class KeyholderTaskChoiceScheduler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(KeyholderTaskChoiceScheduler.class);
|
||||
|
||||
private final KeyholderTaskChoiceRepository keyholderTaskChoiceRepository;
|
||||
private final CardlockRepository cardlockRepository;
|
||||
private final AssignedTaskRepository assignedTaskRepository;
|
||||
private final SystemMessageService systemMessageService;
|
||||
|
||||
public KeyholderTaskChoiceScheduler(KeyholderTaskChoiceRepository keyholderTaskChoiceRepository,
|
||||
CardlockRepository cardlockRepository,
|
||||
AssignedTaskRepository assignedTaskRepository,
|
||||
SystemMessageService systemMessageService) {
|
||||
this.keyholderTaskChoiceRepository = keyholderTaskChoiceRepository;
|
||||
this.cardlockRepository = cardlockRepository;
|
||||
this.assignedTaskRepository = assignedTaskRepository;
|
||||
this.systemMessageService = systemMessageService;
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = 60_000)
|
||||
@Transactional
|
||||
public void processExpiredChoices() {
|
||||
var expired = keyholderTaskChoiceRepository
|
||||
.findByActiveTrueAndExpiresAtBefore(LocalDateTime.now());
|
||||
|
||||
for (var choice : expired) {
|
||||
LOG.debug("Processing expired keyholder task choice {}", choice.getChoiceId());
|
||||
|
||||
var lockOpt = cardlockRepository.findById(choice.getLockId());
|
||||
if (lockOpt.isEmpty()) {
|
||||
choice.setActive(false);
|
||||
keyholderTaskChoiceRepository.save(choice);
|
||||
continue;
|
||||
}
|
||||
var lock = lockOpt.get();
|
||||
List<Task> tasks = lock.getTasks();
|
||||
if (tasks == null || tasks.isEmpty()) {
|
||||
choice.setActive(false);
|
||||
keyholderTaskChoiceRepository.save(choice);
|
||||
continue;
|
||||
}
|
||||
|
||||
int taskIndex = new Random().nextInt(tasks.size());
|
||||
Task task = tasks.get(taskIndex);
|
||||
LOG.debug("Keyholder did not choose in time → random task index {}", taskIndex);
|
||||
|
||||
AssignedTaskEntity assigned = new AssignedTaskEntity();
|
||||
assigned.setLockId(lock.getLockId());
|
||||
assigned.setTaskTitle(task.getTitle());
|
||||
assigned.setTaskDescription(task.getDescription());
|
||||
assigned.setTaskText(task.getTitle());
|
||||
assigned.setTaskMinutes(task.getMinutes());
|
||||
assigned.setAssignedAt(LocalDateTime.now());
|
||||
assigned.setAcceptDeadline(LocalDateTime.now().plusHours(1));
|
||||
assigned.setStatus("PENDING");
|
||||
assignedTaskRepository.save(assigned);
|
||||
|
||||
choice.setActive(false);
|
||||
keyholderTaskChoiceRepository.save(choice);
|
||||
|
||||
sendMessage(lock.getLockee(),
|
||||
"Dein Keyholder hat nicht rechtzeitig gewählt – eine zufällige Aufgabe wurde vergeben: \"" + task.getTitle() + "\"",
|
||||
"/activelock.html?lockId=" + lock.getLockId());
|
||||
|
||||
if (lock.getKeyholder() != null) {
|
||||
sendMessage(lock.getKeyholder(),
|
||||
"Du hast die Aufgabenwahl für \"" + (lock.getName() != null ? lock.getName() : "ein Schloss") + "\" nicht rechtzeitig getroffen. Eine zufällige Aufgabe wurde vergeben.",
|
||||
"/keyholder.html");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessage(UUID toId, String text, String targetUrl) {
|
||||
systemMessageService.send(toId, toId, text, targetUrl, MessageCause.GAME_STATE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package de.oaa.xxx.games.chastity.keyholder;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.oaa.xxx.games.chastity.common.Verification;
|
||||
import de.oaa.xxx.games.chastity.common.VerificationCommonDTO;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "keyholder_verification")
|
||||
public class KeyholderVerificationEntity implements Verification {
|
||||
|
||||
@Id
|
||||
@Column
|
||||
private UUID verificationId;
|
||||
@Column(nullable = false)
|
||||
private UUID lockId;
|
||||
@Column(nullable = false)
|
||||
private String code;
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
@Column(columnDefinition = "MEDIUMBLOB")
|
||||
private byte[] image;
|
||||
@Column(nullable = false)
|
||||
private UUID lockeeId;
|
||||
@Column(nullable = false)
|
||||
private UUID keyholderId;
|
||||
@Column
|
||||
private boolean accepted;
|
||||
|
||||
@Override
|
||||
public LocalDate getVerificationDate() {
|
||||
return createdAt.toLocalDate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return accepted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VerificationCommonDTO toCommonVerification() {
|
||||
return new VerificationCommonDTO(verificationId, lockId, code, createdAt, image, lockeeId, keyholderId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getId() {
|
||||
return verificationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(UUID id) {
|
||||
setVerificationId(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package de.oaa.xxx.games.chastity.keyholder;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface KeyholderVerificationRepository extends JpaRepository<KeyholderVerificationEntity, UUID>{
|
||||
|
||||
java.util.List<KeyholderVerificationEntity> findByLockId(UUID lockId);
|
||||
}
|
||||
@@ -2,12 +2,12 @@ package de.oaa.xxx.games.chastity.lockcontroll;
|
||||
|
||||
public class TTLockControl extends LockControl {
|
||||
|
||||
// private static final String BASE_URL = "https://euapi.ttlock.com/";
|
||||
|
||||
public TTLockControl() {
|
||||
super(new NoInteractionCallback());
|
||||
}
|
||||
|
||||
private static final String BASE_URL = "https://euapi.ttlock.com/";
|
||||
|
||||
@Override
|
||||
public boolean init() {
|
||||
// TODO Auto-generated method stub
|
||||
@@ -26,7 +26,4 @@ public class TTLockControl extends LockControl {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public class UnlockcodeLockControl extends LockControl {
|
||||
|
||||
@Override
|
||||
public boolean lock() {
|
||||
var code = CodeCreator.createAlphanumericCode(callback.getUnlockcodeLenght());
|
||||
var code = CodeCreator.createNumeric(callback.getUnlockcodeLenght());
|
||||
callback.setUnlockCode(code);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -21,7 +22,12 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import de.oaa.xxx.games.chastity.cardlock.CardLockEntity;
|
||||
import de.oaa.xxx.games.chastity.cardlock.CardlockRepository;
|
||||
import de.oaa.xxx.games.chastity.common.BaseLockRepository;
|
||||
import de.oaa.xxx.games.chastity.common.CodeCreator;
|
||||
import de.oaa.xxx.games.chastity.timelock.TimeLockEntity;
|
||||
import de.oaa.xxx.games.chastity.timelock.TimeLockRepository;
|
||||
import de.oaa.xxx.social.SystemMessageService;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
|
||||
@@ -31,6 +37,8 @@ public class LockeeInvitationController {
|
||||
|
||||
private final LockeeInvitationRepository lockeeInvitationRepository;
|
||||
private final CardlockRepository cardlockRepository;
|
||||
private final BaseLockRepository baseLockRepository;
|
||||
private final TimeLockRepository timeLockRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final SystemMessageService systemMessageService;
|
||||
|
||||
@@ -41,10 +49,14 @@ public class LockeeInvitationController {
|
||||
|
||||
public LockeeInvitationController(LockeeInvitationRepository lockeeInvitationRepository,
|
||||
CardlockRepository cardlockRepository,
|
||||
BaseLockRepository baseLockRepository,
|
||||
TimeLockRepository timeLockRepository,
|
||||
UserRepository userRepository,
|
||||
SystemMessageService systemMessageService) {
|
||||
this.lockeeInvitationRepository = lockeeInvitationRepository;
|
||||
this.cardlockRepository = cardlockRepository;
|
||||
this.baseLockRepository = baseLockRepository;
|
||||
this.timeLockRepository = timeLockRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.systemMessageService = systemMessageService;
|
||||
}
|
||||
@@ -59,6 +71,12 @@ public class LockeeInvitationController {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private int randomBetween(Integer min, Integer max) {
|
||||
if (max == null) max = 60;
|
||||
if (min == null || min >= max) return max;
|
||||
return min + new Random().nextInt(max - min);
|
||||
}
|
||||
|
||||
@GetMapping("/invitations/mine")
|
||||
public ResponseEntity<List<Map<String, Object>>> getMyInvitations(Principal principal) {
|
||||
var meOpt = userRepository.findByEmail(principal.getName());
|
||||
@@ -68,7 +86,7 @@ public class LockeeInvitationController {
|
||||
var invitations = lockeeInvitationRepository.findByLockeeUserId(myId);
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (var inv : invitations) {
|
||||
var lockOpt = cardlockRepository.findById(inv.getLockId());
|
||||
var lockOpt = baseLockRepository.findById(inv.getLockId());
|
||||
if (lockOpt.isEmpty()) continue;
|
||||
var lock = lockOpt.get();
|
||||
if (lock.getStartTime() != null) continue; // already accepted
|
||||
@@ -97,7 +115,7 @@ public class LockeeInvitationController {
|
||||
var invitations = lockeeInvitationRepository.findByKeyholderUserId(myId);
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (var inv : invitations) {
|
||||
var lockOpt = cardlockRepository.findById(inv.getLockId());
|
||||
var lockOpt = baseLockRepository.findById(inv.getLockId());
|
||||
if (lockOpt.isEmpty()) continue;
|
||||
var lock = lockOpt.get();
|
||||
if (lock.getStartTime() != null) continue; // already accepted
|
||||
@@ -130,12 +148,12 @@ public class LockeeInvitationController {
|
||||
var inv = invOpt.get();
|
||||
if (!inv.getKeyholderUserId().equals(myId)) return ResponseEntity.status(403).build();
|
||||
|
||||
var lockOpt = cardlockRepository.findById(inv.getLockId());
|
||||
var lockOpt = baseLockRepository.findById(inv.getLockId());
|
||||
lockeeInvitationRepository.delete(inv);
|
||||
|
||||
if (lockOpt.isPresent()) {
|
||||
var lock = lockOpt.get();
|
||||
cardlockRepository.delete(lock);
|
||||
baseLockRepository.delete(lock);
|
||||
String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock";
|
||||
sendMessage(myId, inv.getLockeeUserId(),
|
||||
me.getName() + " hat die Lockee-Einladung für das Lock „" + lockName + "\" zurückgezogen.",
|
||||
@@ -156,7 +174,7 @@ public class LockeeInvitationController {
|
||||
var inv = invOpt.get();
|
||||
if (!inv.getLockeeUserId().equals(myId)) return ResponseEntity.status(403).build();
|
||||
|
||||
var lockOpt = cardlockRepository.findById(inv.getLockId());
|
||||
var lockOpt = baseLockRepository.findById(inv.getLockId());
|
||||
if (lockOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
var lock = lockOpt.get();
|
||||
|
||||
@@ -173,18 +191,18 @@ public class LockeeInvitationController {
|
||||
result.put("createdAt", inv.getCreatedAt().toString());
|
||||
result.put("detailsVisible", inv.isDetailsVisible());
|
||||
|
||||
if (inv.isDetailsVisible() && lock.getInitialCards() != null) {
|
||||
Map<String, Long> cardCounts = lock.getInitialCards().stream()
|
||||
if (inv.isDetailsVisible() && lock instanceof CardLockEntity cardLock && cardLock.getInitialCards() != null) {
|
||||
Map<String, Long> cardCounts = cardLock.getInitialCards().stream()
|
||||
.collect(java.util.stream.Collectors.groupingBy(
|
||||
c -> c.name(), java.util.stream.Collectors.counting()));
|
||||
result.put("cardCounts", cardCounts);
|
||||
result.put("pickEveryMinute", lock.getPickEveryMinute());
|
||||
result.put("accumulatePicks", lock.isAccumulatePicks());
|
||||
result.put("showRemainingCards", lock.isShowRemainingCards());
|
||||
result.put("hygineOpeningEveryMinites", lock.getHygineOpeningEveryMinites());
|
||||
result.put("hygineOpeningDurationMinutes", lock.getHygineOpeningDurationMinutes());
|
||||
result.put("requiresVerification", lock.isRequiresVerification());
|
||||
result.put("taskCount", lock.getTasks() != null ? lock.getTasks().size() : 0);
|
||||
result.put("pickEveryMinute", cardLock.getPickEveryMinute());
|
||||
result.put("accumulatePicks", cardLock.isAccumulatePicks());
|
||||
result.put("showRemainingCards", cardLock.isShowRemainingCards());
|
||||
result.put("hygineOpeningEveryMinites", cardLock.getHygineOpeningEveryMinites());
|
||||
result.put("hygineOpeningDurationMinutes", cardLock.getHygineOpeningDurationMinutes());
|
||||
result.put("requiresVerification", cardLock.isRequiresVerification());
|
||||
result.put("taskCount", cardLock.getTasks() != null ? cardLock.getTasks().size() : 0);
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(result);
|
||||
@@ -207,30 +225,52 @@ public class LockeeInvitationController {
|
||||
var inv = invOpt.get();
|
||||
if (!inv.getLockeeUserId().equals(myId)) return ResponseEntity.status(403).build();
|
||||
|
||||
var lockOpt = cardlockRepository.findById(inv.getLockId());
|
||||
var lockOpt = baseLockRepository.findById(inv.getLockId());
|
||||
if (lockOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
var lock = lockOpt.get();
|
||||
if (lock.getStartTime() != null) return ResponseEntity.status(409).body(Map.of("error", "already_accepted"));
|
||||
if (cardlockRepository.existsByLockeeAndStartTimeIsNotNullAndUnlockTimeIsNull(myId))
|
||||
|
||||
if (cardlockRepository.existsByLockeeAndStartTimeIsNotNullAndUnlockTimeIsNull(myId)
|
||||
|| timeLockRepository.existsByLockeeAndStartTimeIsNotNullAndUnlockTimeIsNull(myId))
|
||||
return ResponseEntity.status(409).body(Map.of("error", "active_lock_exists"));
|
||||
|
||||
int codeLines = (req.unlockCodeLines() != null && req.unlockCodeLines() >= 1) ? req.unlockCodeLines() : 5;
|
||||
String unlockCode = generateUnlockCode(codeLines);
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
lock.setStartTime(now);
|
||||
lock.setUnlockCode(unlockCode);
|
||||
lock.setUnlockCodeLength(codeLines);
|
||||
lock.setAvailableCards(new ArrayList<>(lock.getInitialCards()));
|
||||
lock.setOpenPicks(0);
|
||||
lock.setNextCardIn(now.plusMinutes(lock.getPickEveryMinute()));
|
||||
if (lock.getHygineOpeningEveryMinites() != null) {
|
||||
lock.setLastHygineOpening(now);
|
||||
|
||||
String unlockCode;
|
||||
String lockName;
|
||||
|
||||
if (lock instanceof CardLockEntity cardLock) {
|
||||
unlockCode = generateUnlockCode(codeLines);
|
||||
cardLock.setStartTime(now);
|
||||
cardLock.setUnlockCode(unlockCode);
|
||||
cardLock.setUnlockCodeLength(codeLines);
|
||||
cardLock.setAvailableCards(new ArrayList<>(cardLock.getInitialCards()));
|
||||
cardLock.setOpenPicks(0);
|
||||
cardLock.setNextCardIn(now.plusMinutes(cardLock.getPickEveryMinute()));
|
||||
if (cardLock.getHygineOpeningEveryMinites() != null) {
|
||||
cardLock.setLastHygineOpening(now);
|
||||
}
|
||||
cardlockRepository.save(lock);
|
||||
cardlockRepository.save(cardLock);
|
||||
lockName = cardLock.getName() != null && !cardLock.getName().isBlank() ? cardLock.getName() : "Unbenanntes Lock";
|
||||
} else if (lock instanceof TimeLockEntity timeLock) {
|
||||
unlockCode = CodeCreator.createNumeric(codeLines);
|
||||
int unlockMinutes = randomBetween(timeLock.getMinTimeInMinutes(), timeLock.getMaxTimeInMinutes());
|
||||
timeLock.setStartTime(now);
|
||||
timeLock.setUnlockTime(now.plusMinutes(unlockMinutes));
|
||||
timeLock.setUnlockCode(unlockCode);
|
||||
timeLock.setUnlockCodeLength(codeLines);
|
||||
if (timeLock.getHygineOpeningEveryMinites() != null) {
|
||||
timeLock.setLastHygineOpening(now);
|
||||
}
|
||||
timeLockRepository.save(timeLock);
|
||||
lockName = timeLock.getName() != null && !timeLock.getName().isBlank() ? timeLock.getName() : "Unbenanntes Lock";
|
||||
} else {
|
||||
return ResponseEntity.status(500).build();
|
||||
}
|
||||
|
||||
lockeeInvitationRepository.delete(inv);
|
||||
|
||||
String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock";
|
||||
sendMessage(myId, inv.getKeyholderUserId(),
|
||||
me.getName() + " hat die Einladung als Lockee für das Lock „" + lockName + "\" angenommen.",
|
||||
"/keyholder.html", de.oaa.xxx.social.entity.MessageCause.INVITATION);
|
||||
@@ -254,12 +294,12 @@ public class LockeeInvitationController {
|
||||
var inv = invOpt.get();
|
||||
if (!inv.getLockeeUserId().equals(myId)) return ResponseEntity.status(403).build();
|
||||
|
||||
var lockOpt = cardlockRepository.findById(inv.getLockId());
|
||||
var lockOpt = baseLockRepository.findById(inv.getLockId());
|
||||
lockeeInvitationRepository.delete(inv);
|
||||
|
||||
if (lockOpt.isPresent()) {
|
||||
var lock = lockOpt.get();
|
||||
cardlockRepository.delete(lock);
|
||||
baseLockRepository.delete(lock);
|
||||
String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock";
|
||||
sendMessage(myId, inv.getKeyholderUserId(),
|
||||
me.getName() + " hat die Einladung als Lockee für das Lock „" + lockName + "\" abgelehnt.",
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package de.oaa.xxx.games.chastity.pillory;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "pillory")
|
||||
public class PilloryEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
@Column
|
||||
@Setter(lombok.AccessLevel.NONE)
|
||||
private UUID pilloryId;
|
||||
@Column(nullable = false)
|
||||
private UUID lockId;
|
||||
@Column(nullable = false)
|
||||
private UUID lockeeUserId;
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
@Column
|
||||
private PilloryReason reason;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package de.oaa.xxx.games.chastity.pillory;
|
||||
|
||||
public enum PilloryReason {
|
||||
|
||||
HYGIENE_OPENING_EXEEDED,
|
||||
KEYHOLDER_DESCESSION;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package de.oaa.xxx.games.chastity.pillory;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface PilloryRepository extends JpaRepository<PilloryEntity, UUID> {
|
||||
|
||||
}
|
||||
@@ -11,16 +11,6 @@ public class Task {
|
||||
private String description;
|
||||
private Integer minutes;
|
||||
|
||||
/** @deprecated Backward-Compat – alte Einträge ohne title/description. Nur lesen, nicht setzen. */
|
||||
private String text;
|
||||
|
||||
/** Gibt den anzeigbaren Titel zurück – fällt auf altes text-Feld zurück. */
|
||||
public String resolveTitle() {
|
||||
if (title != null && !title.isBlank()) return title;
|
||||
if (text != null && !text.isBlank()) return text;
|
||||
return "Aufgabe";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Task[title=" + title + ", minutes=" + minutes + "]";
|
||||
|
||||
@@ -0,0 +1,645 @@
|
||||
package de.oaa.xxx.games.chastity.timelock;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
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.community.CommunityVerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationVoteEntity;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationVoteRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderInvitationEntity;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderInvitationRepository;
|
||||
import de.oaa.xxx.games.chastity.lockcontroll.LockControllType;
|
||||
import de.oaa.xxx.games.chastity.lockee.LockeeInvitationEntity;
|
||||
import de.oaa.xxx.games.chastity.lockee.LockeeInvitationRepository;
|
||||
import de.oaa.xxx.games.chastity.spinningwheel.SpinningWheelEntry;
|
||||
import de.oaa.xxx.games.chastity.unlock.TempOpeningReason;
|
||||
import de.oaa.xxx.social.SystemMessageService;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/keyholder")
|
||||
public class TimeLockController {
|
||||
|
||||
private final TimeLockRepository timeLockRepository;
|
||||
private final TimeLockTemplateRepository templateRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final KeyholderInvitationRepository invitationRepository;
|
||||
private final LockeeInvitationRepository lockeeInvitationRepository;
|
||||
private final SystemMessageService systemMessageService;
|
||||
private final TimeLockServiceFactory timeLockServiceFactory;
|
||||
private final CommunityVerificationRepository verificationRepository;
|
||||
private final CommunityVerificationVoteRepository verificationVoteRepository;
|
||||
|
||||
public TimeLockController(TimeLockRepository timeLockRepository,
|
||||
TimeLockTemplateRepository templateRepository,
|
||||
UserRepository userRepository,
|
||||
KeyholderInvitationRepository invitationRepository,
|
||||
LockeeInvitationRepository lockeeInvitationRepository,
|
||||
SystemMessageService systemMessageService,
|
||||
TimeLockServiceFactory timeLockServiceFactory,
|
||||
CommunityVerificationRepository verificationRepository,
|
||||
CommunityVerificationVoteRepository verificationVoteRepository) {
|
||||
this.timeLockRepository = timeLockRepository;
|
||||
this.templateRepository = templateRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.invitationRepository = invitationRepository;
|
||||
this.lockeeInvitationRepository = lockeeInvitationRepository;
|
||||
this.systemMessageService = systemMessageService;
|
||||
this.timeLockServiceFactory = timeLockServiceFactory;
|
||||
this.verificationRepository = verificationRepository;
|
||||
this.verificationVoteRepository = verificationVoteRepository;
|
||||
}
|
||||
|
||||
// ── Erstellen ────────────────────────────────────────────────────────────────
|
||||
|
||||
record CreateTimeLockRequest(
|
||||
UUID templateId,
|
||||
UUID lockeeUserId,
|
||||
boolean lockeeDetailsVisible,
|
||||
UUID keyholder,
|
||||
boolean testLock,
|
||||
Integer unlockCodeLength,
|
||||
LockControllType controllType
|
||||
) {}
|
||||
|
||||
@PostMapping("/timelock")
|
||||
@Transactional
|
||||
public ResponseEntity<Map<String, Object>> createTimeLock(
|
||||
@RequestBody CreateTimeLockRequest 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 templateOpt = templateRepository.findById(req.templateId());
|
||||
if (templateOpt.isEmpty()) return ResponseEntity.badRequest().build();
|
||||
var template = templateOpt.get();
|
||||
|
||||
int codeLen = (req.unlockCodeLength() != null && req.unlockCodeLength() >= 1) ? req.unlockCodeLength() : 5;
|
||||
boolean friendLockee = req.lockeeUserId() != null && !req.lockeeUserId().equals(myId);
|
||||
|
||||
if (friendLockee) {
|
||||
var lockeeOpt = userRepository.findById(req.lockeeUserId());
|
||||
if (lockeeOpt.isEmpty()) return ResponseEntity.badRequest().build();
|
||||
var lockee = lockeeOpt.get();
|
||||
|
||||
TimeLockEntity lock = buildBaseEntity(template, myId, req.lockeeUserId(), false);
|
||||
lock.setStartTime(null);
|
||||
lock.setUnlockTime(null);
|
||||
timeLockRepository.save(lock);
|
||||
|
||||
String token = UUID.randomUUID().toString().replace("-", "");
|
||||
LockeeInvitationEntity inv = new LockeeInvitationEntity();
|
||||
inv.setLockId(lock.getLockId());
|
||||
inv.setLockeeUserId(lockee.getUserId());
|
||||
inv.setKeyholderUserId(myId);
|
||||
inv.setToken(token);
|
||||
inv.setCreatedAt(LocalDateTime.now());
|
||||
inv.setDetailsVisible(req.lockeeDetailsVisible());
|
||||
lockeeInvitationRepository.save(inv);
|
||||
|
||||
String lockName = template.getName() != null ? template.getName() : "Unbenanntes Lock";
|
||||
systemMessageService.send(myId, lockee.getUserId(),
|
||||
me.getName() + " hat dich als Lockee für das Lock „" + lockName + "\" eingeladen.",
|
||||
"/einladungen.html", de.oaa.xxx.social.entity.MessageCause.INVITATION);
|
||||
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"lockId", lock.getLockId().toString(),
|
||||
"lockeeInvitationSent", true));
|
||||
}
|
||||
|
||||
if (timeLockRepository.existsByLockeeAndStartTimeIsNotNullAndUnlockTimeIsNull(myId))
|
||||
return ResponseEntity.status(409).body(Map.of("error", "active_lock_exists"));
|
||||
|
||||
TimeLockAdditionalSettings settings = new TimeLockAdditionalSettings(
|
||||
req.controllType() != null ? req.controllType() : LockControllType.UNLOCK_CODE,
|
||||
myId, req.keyholder(), req.testLock(), codeLen);
|
||||
TimeLockEntity lock = new TimeLockEntity();
|
||||
timeLockServiceFactory.create(lock).init(template, settings);
|
||||
timeLockRepository.save(lock); // Sicherstellen dass auch TRUST-Locks persistiert sind
|
||||
|
||||
boolean keyholderPending = false;
|
||||
if (req.keyholder() != null) {
|
||||
var khOpt = userRepository.findById(req.keyholder());
|
||||
if (khOpt.isPresent()) {
|
||||
var kh = khOpt.get();
|
||||
KeyholderInvitationEntity inv = new KeyholderInvitationEntity();
|
||||
inv.setLockId(lock.getLockId());
|
||||
inv.setKeyholderUserId(kh.getUserId());
|
||||
inv.setLockeeUserId(myId);
|
||||
inv.setToken(UUID.randomUUID().toString().replace("-", ""));
|
||||
inv.setCreatedAt(LocalDateTime.now());
|
||||
invitationRepository.save(inv);
|
||||
|
||||
String lockName = template.getName() != null ? template.getName() : "Unbenanntes Lock";
|
||||
systemMessageService.send(myId, kh.getUserId(),
|
||||
me.getName() + " hat dich als Keyholder*In für das Lock „" + lockName + "\" eingeladen.",
|
||||
"/einladungen.html", de.oaa.xxx.social.entity.MessageCause.INVITATION);
|
||||
|
||||
keyholderPending = true;
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"lockId", lock.getLockId().toString(),
|
||||
"unlockCode", lock.getUnlockCode() != null ? lock.getUnlockCode() : "",
|
||||
"keyholderPending", keyholderPending));
|
||||
}
|
||||
|
||||
// ── State abrufen ────────────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/timelock/{lockId}")
|
||||
@Transactional
|
||||
public ResponseEntity<Map<String, Object>> getTimeLock(@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 = timeLockRepository.findById(lockId);
|
||||
if (lockOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
var l = lockOpt.get();
|
||||
if (!l.getLockee().equals(myId)) return ResponseEntity.status(403).build();
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
// Periodic check (daily violations)
|
||||
TimeLockService service = timeLockServiceFactory.create(l);
|
||||
service.check();
|
||||
|
||||
// Auto-unfreeze when frozenUntil has passed
|
||||
if (l.getFrozenUntil() != null && l.getFrozenUntil().isBefore(now) && l.getFrozenFrom() != null) {
|
||||
service.unfreeze();
|
||||
l.setFrozenFrom(null);
|
||||
l.setFrozenUntil(null);
|
||||
timeLockRepository.save(l);
|
||||
}
|
||||
|
||||
// Emergency auto-unlock after 1h
|
||||
if (l.getEmergencyUnlockRequestedAt() != null && !l.isKeyholderRequestedUnlock()
|
||||
&& l.getEmergencyUnlockRequestedAt().isBefore(now.minusHours(1))) {
|
||||
l.setEmergencyAutoUnlocked(true);
|
||||
l.setKeyholderRequestedUnlock(true);
|
||||
timeLockRepository.save(l);
|
||||
}
|
||||
|
||||
boolean timeUp = l.getUnlockTime() != null && l.getUnlockTime().isBefore(now);
|
||||
boolean isFrozen = l.getFrozenFrom() != null
|
||||
&& (l.getFrozenUntil() == null || l.getFrozenUntil().isAfter(now));
|
||||
|
||||
// Hygiene state
|
||||
boolean hygieneEnabled = l.getHygineOpeningEveryMinites() != null;
|
||||
boolean hygieneOpeningDue = false;
|
||||
long hygieneSecondsRemaining = 0;
|
||||
boolean hygieneOpeningActive = l.getTempOpeningTime() != null
|
||||
&& TempOpeningReason.HYGIENE == l.getTempOpeningReason();
|
||||
if (hygieneEnabled && !hygieneOpeningActive) {
|
||||
LocalDateTime lastH = l.getLastHygineOpening();
|
||||
if (lastH == null) {
|
||||
hygieneOpeningDue = true;
|
||||
} else {
|
||||
LocalDateTime nextH = lastH.plusMinutes(l.getHygineOpeningEveryMinites());
|
||||
long secs = ChronoUnit.SECONDS.between(now, nextH);
|
||||
if (secs <= 0) hygieneOpeningDue = true;
|
||||
else hygieneSecondsRemaining = secs;
|
||||
}
|
||||
}
|
||||
|
||||
// Spin wheel state
|
||||
boolean spinEnabled = l.getSpinsEveryMinutes() != null
|
||||
&& l.getSpinningWheelEntries() != null && !l.getSpinningWheelEntries().isEmpty();
|
||||
boolean spinDue = false;
|
||||
String nextSpinIn = null;
|
||||
if (spinEnabled) {
|
||||
List<LocalDateTime> times = l.getSpinningWheelTimes();
|
||||
if (times == null || times.isEmpty()) {
|
||||
spinDue = true;
|
||||
} else {
|
||||
LocalDateTime next = times.get(times.size() - 1).plusMinutes(l.getSpinsEveryMinutes());
|
||||
if (next.isBefore(now)) spinDue = true;
|
||||
else nextSpinIn = next.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Task timing state
|
||||
boolean taskTimingEnabled = l.getTaskEveryMinutes() != null;
|
||||
String nextTaskIn = null;
|
||||
if (taskTimingEnabled && l.getCurrentTask() == null) {
|
||||
List<LocalDateTime> times = l.getTaskTimes();
|
||||
LocalDateTime next;
|
||||
if (times == null || times.isEmpty()) {
|
||||
next = l.getStartTime() != null
|
||||
? l.getStartTime().plusMinutes(l.getTaskEveryMinutes()) : null;
|
||||
} else {
|
||||
next = times.get(times.size() - 1).plusMinutes(l.getTaskEveryMinutes());
|
||||
}
|
||||
if (next != null && next.isAfter(now)) nextTaskIn = next.toString();
|
||||
}
|
||||
|
||||
// Keyholder info
|
||||
boolean keyholderInvitationPending =
|
||||
l.getKeyholder() == null && !invitationRepository.findByLockId(lockId).isEmpty();
|
||||
|
||||
// Verification state
|
||||
boolean verificationDue = false;
|
||||
String verificationPendingId = null;
|
||||
String verificationPendingCode = null;
|
||||
long verificationUpvotes = 0;
|
||||
long verificationDownvotes = 0;
|
||||
if (l.isRequiresVerification()) {
|
||||
LocalDateTime todayStart = LocalDate.now().atStartOfDay();
|
||||
LocalDateTime todayEnd = todayStart.plusDays(1);
|
||||
var completed = verificationRepository
|
||||
.findByLockIdAndCreatedAtBetweenAndImageIsNotNull(lockId, todayStart, todayEnd);
|
||||
if (!completed.isEmpty()) {
|
||||
var todayV = completed.get(0);
|
||||
var votes = verificationVoteRepository.findAllByVerificationId(todayV.getDisplayId());
|
||||
verificationUpvotes = votes.stream().filter(CommunityVerificationVoteEntity::isUpvote).count();
|
||||
verificationDownvotes = votes.stream().filter(v -> !v.isUpvote()).count();
|
||||
} else {
|
||||
verificationDue = true;
|
||||
var pending = verificationRepository
|
||||
.findByLockIdAndCreatedAtBetweenAndImageIsNull(lockId, todayStart, todayEnd);
|
||||
if (!pending.isEmpty()) {
|
||||
verificationPendingId = pending.get(0).getDisplayId().toString();
|
||||
verificationPendingCode = pending.get(0).getCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("lockId", l.getLockId().toString());
|
||||
result.put("name", l.getName() != null ? l.getName() : "");
|
||||
result.put("testLock", l.isTestLock());
|
||||
result.put("startTime", l.getStartTime() != null ? l.getStartTime().toString() : null);
|
||||
result.put("endTimeVisible", l.isEndTimeVisible());
|
||||
result.put("timeUp", timeUp);
|
||||
result.put("isFrozen", isFrozen);
|
||||
result.put("frozenUntil", l.getFrozenUntil() != null ? l.getFrozenUntil().toString() : null);
|
||||
|
||||
// Only expose unlock time if end time is visible OR time is up
|
||||
if (l.isEndTimeVisible() || timeUp) {
|
||||
result.put("unlockTime", l.getUnlockTime() != null ? l.getUnlockTime().toString() : null);
|
||||
} else {
|
||||
result.put("unlockTime", null);
|
||||
}
|
||||
|
||||
result.put("currentTask", l.getCurrentTask());
|
||||
result.put("currentTaskDescription", l.getCurrentTaskDescription());
|
||||
result.put("taskUntil", l.getTaskUntil() != null ? l.getTaskUntil().toString() : null);
|
||||
|
||||
result.put("spinEnabled", spinEnabled);
|
||||
result.put("spinDue", spinDue);
|
||||
result.put("nextSpinIn", nextSpinIn);
|
||||
|
||||
result.put("taskTimingEnabled", taskTimingEnabled);
|
||||
result.put("nextTaskIn", nextTaskIn);
|
||||
|
||||
result.put("hygieneEnabled", hygieneEnabled);
|
||||
result.put("hygieneOpeningDue", hygieneOpeningDue);
|
||||
result.put("hygieneSecondsRemaining", hygieneSecondsRemaining);
|
||||
result.put("hygieneOpeningActive", hygieneOpeningActive);
|
||||
result.put("hygieneOpeningStarted", l.getTempOpeningTime() != null ? l.getTempOpeningTime().toString() : null);
|
||||
result.put("hygieneDurationMinutes", l.getHygineOpeningDurationMinutes() != null ? l.getHygineOpeningDurationMinutes() : 0);
|
||||
|
||||
result.put("verificationRequired", l.isRequiresVerification());
|
||||
result.put("verificationDue", verificationDue);
|
||||
result.put("verificationPendingId", verificationPendingId);
|
||||
result.put("verificationPendingCode", verificationPendingCode);
|
||||
result.put("verificationUpvotes", verificationUpvotes);
|
||||
result.put("verificationDownvotes", verificationDownvotes);
|
||||
|
||||
result.put("hasKeyholder", l.getKeyholder() != null);
|
||||
result.put("keyholderInvitationPending", keyholderInvitationPending);
|
||||
if (l.getKeyholder() != null) {
|
||||
userRepository.findById(l.getKeyholder()).ifPresent(kh -> {
|
||||
result.put("keyholderName", kh.getName());
|
||||
result.put("keyholderUserId", kh.getUserId().toString());
|
||||
result.put("keyholderProfilePic", kh.getProfilePicture());
|
||||
});
|
||||
}
|
||||
|
||||
result.put("keyholderRequestedUnlock", l.isKeyholderRequestedUnlock());
|
||||
if (l.isKeyholderRequestedUnlock() || l.isTestLock()) {
|
||||
result.put("unlockCode", l.getUnlockCode() != null ? l.getUnlockCode() : "");
|
||||
}
|
||||
result.put("emergencyUnlockRequested", l.getEmergencyUnlockRequestedAt() != null);
|
||||
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
// ── Glücksrad drehen ─────────────────────────────────────────────────────────
|
||||
|
||||
@PostMapping("/timelock/{lockId}/spin")
|
||||
@Transactional
|
||||
public ResponseEntity<Map<String, Object>> spin(@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 = timeLockRepository.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.getUnlockTime() == null) return ResponseEntity.status(409).build(); // not started
|
||||
if (l.getSpinningWheelEntries() == null || l.getSpinningWheelEntries().isEmpty())
|
||||
return ResponseEntity.status(409).build();
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
boolean isFrozen = l.getFrozenFrom() != null
|
||||
&& (l.getFrozenUntil() == null || l.getFrozenUntil().isAfter(now));
|
||||
if (isFrozen) return ResponseEntity.status(409).body(Map.of("error", "frozen"));
|
||||
if (TempOpeningReason.HYGIENE == l.getTempOpeningReason())
|
||||
return ResponseEntity.status(409).body(Map.of("error", "hygiene_opening"));
|
||||
|
||||
// Check spin is due
|
||||
List<LocalDateTime> spinTimes = l.getSpinningWheelTimes();
|
||||
if (spinTimes != null && !spinTimes.isEmpty() && l.getSpinsEveryMinutes() != null) {
|
||||
LocalDateTime next = spinTimes.get(spinTimes.size() - 1).plusMinutes(l.getSpinsEveryMinutes());
|
||||
if (next.isAfter(now)) return ResponseEntity.status(409).body(Map.of("error", "not_due"));
|
||||
}
|
||||
|
||||
TimeLockService service = timeLockServiceFactory.create(l);
|
||||
SpinningWheelEntry entry = service.spinWheel();
|
||||
if (entry == null) return ResponseEntity.status(409).body(Map.of("error", "no_entry"));
|
||||
|
||||
// Record spin time
|
||||
if (l.getSpinningWheelTimes() == null) l.setSpinningWheelTimes(new ArrayList<>());
|
||||
l.getSpinningWheelTimes().add(now);
|
||||
timeLockRepository.save(l);
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("type", entry.getType().name());
|
||||
result.put("intVal", entry.getIntVal());
|
||||
result.put("stringVal", entry.getStringVal());
|
||||
// Include updated lock time fields
|
||||
result.put("newUnlockTime", l.getUnlockTime() != null ? l.getUnlockTime().toString() : null);
|
||||
result.put("newFrozenUntil", l.getFrozenUntil() != null ? l.getFrozenUntil().toString() : null);
|
||||
result.put("isFrozen", l.getFrozenFrom() != null);
|
||||
result.put("currentTask", l.getCurrentTask());
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
// ── Aufgabe erledigt ──────────────────────────────────────────────────────────
|
||||
|
||||
@PostMapping("/timelock/{lockId}/task/done")
|
||||
@Transactional
|
||||
public ResponseEntity<Void> taskDone(@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 = timeLockRepository.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.getCurrentTask() == null) return ResponseEntity.status(409).build();
|
||||
|
||||
// Check task timer hasn't expired (still locked)
|
||||
if (l.getTaskUntil() != null && l.getTaskUntil().isAfter(LocalDateTime.now()))
|
||||
return ResponseEntity.status(409).body(null);
|
||||
|
||||
timeLockServiceFactory.create(l).clearTask();
|
||||
timeLockRepository.save(l);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
// ── Hygiene-Öffnung starten ───────────────────────────────────────────────────
|
||||
|
||||
@PostMapping("/timelock/{lockId}/hygiene/start")
|
||||
@Transactional
|
||||
public ResponseEntity<Map<String, Object>> startHygiene(@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 = timeLockRepository.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.getHygineOpeningEveryMinites() == null) return ResponseEntity.status(409).build();
|
||||
if (l.getTempOpeningTime() != null) return ResponseEntity.status(409).body(Map.of("error", "already_open"));
|
||||
|
||||
TimeLockService service = timeLockServiceFactory.create(l);
|
||||
service.startHygieneOpening();
|
||||
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"unlockCode", l.getUnlockCode() != null ? l.getUnlockCode() : "",
|
||||
"durationMinutes", l.getHygineOpeningDurationMinutes() != null ? l.getHygineOpeningDurationMinutes() : 0,
|
||||
"openedAt", l.getTempOpeningTime() != null ? l.getTempOpeningTime().toString() : ""));
|
||||
}
|
||||
|
||||
// ── Hygiene-Öffnung beenden ───────────────────────────────────────────────────
|
||||
|
||||
@PostMapping("/timelock/{lockId}/hygiene/end")
|
||||
@Transactional
|
||||
public ResponseEntity<Map<String, Object>> endHygiene(@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 = timeLockRepository.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.getTempOpeningTime() == null || TempOpeningReason.HYGIENE != l.getTempOpeningReason())
|
||||
return ResponseEntity.status(409).build();
|
||||
|
||||
TimeLockService service = timeLockServiceFactory.create(l);
|
||||
String newCode = service.endHygieneOpening();
|
||||
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"newUnlockCode", newCode,
|
||||
"newUnlockTime", l.getUnlockTime() != null ? l.getUnlockTime().toString() : ""));
|
||||
}
|
||||
|
||||
// ── Verifikation starten ─────────────────────────────────────────────────────
|
||||
|
||||
@PostMapping("/timelock/{lockId}/verification/start")
|
||||
@Transactional
|
||||
public ResponseEntity<Map<String, Object>> startVerification(@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 = timeLockRepository.findById(lockId);
|
||||
if (lockOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
var l = lockOpt.get();
|
||||
if (!l.getLockee().equals(myId)) return ResponseEntity.status(403).build();
|
||||
|
||||
var dto = timeLockServiceFactory.create(l).startVerification();
|
||||
return ResponseEntity.ok(Map.of("verificationId", dto.verficationId().toString(), "code", dto.code()));
|
||||
}
|
||||
|
||||
// ── Verifikation abschließen ──────────────────────────────────────────────────
|
||||
|
||||
@PostMapping(value = "/timelock/{lockId}/verification/{verificationId}/complete",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@Transactional
|
||||
public ResponseEntity<Void> completeVerification(
|
||||
@PathVariable UUID lockId,
|
||||
@PathVariable UUID verificationId,
|
||||
@RequestParam MultipartFile image,
|
||||
Principal principal) throws IOException {
|
||||
|
||||
var meOpt = userRepository.findByEmail(principal.getName());
|
||||
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||
UUID myId = meOpt.get().getUserId();
|
||||
|
||||
var lockOpt = timeLockRepository.findById(lockId);
|
||||
if (lockOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
var l = lockOpt.get();
|
||||
if (!l.getLockee().equals(myId)) return ResponseEntity.status(403).build();
|
||||
|
||||
boolean found = timeLockServiceFactory.create(l).completeVerification(verificationId, scaleImage(image.getBytes(), 1024));
|
||||
if (!found) return ResponseEntity.notFound().build();
|
||||
|
||||
if (l.getKeyholder() != null) {
|
||||
systemMessageService.send(myId, l.getKeyholder(),
|
||||
"📸 " + meOpt.get().getName() + " hat eine Verifikation eingereicht.",
|
||||
"/keyholder.html", de.oaa.xxx.social.entity.MessageCause.GAME_STATE);
|
||||
}
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
// ── Lock beenden (Zeit abgelaufen / Test-Lock) ────────────────────────────────
|
||||
|
||||
@DeleteMapping("/timelock/{lockId}")
|
||||
@Transactional
|
||||
public ResponseEntity<Void> endLock(@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 = timeLockRepository.findById(lockId);
|
||||
if (lockOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
var l = lockOpt.get();
|
||||
if (!l.getLockee().equals(myId)) return ResponseEntity.status(403).build();
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
boolean timeUp = l.getUnlockTime() != null && l.getUnlockTime().isBefore(now);
|
||||
boolean isFrozen = l.getFrozenFrom() != null
|
||||
&& (l.getFrozenUntil() == null || l.getFrozenUntil().isAfter(now));
|
||||
|
||||
if (!l.isTestLock() && !timeUp && !l.isKeyholderRequestedUnlock()) {
|
||||
return ResponseEntity.status(409).build(); // Not yet unlockable
|
||||
}
|
||||
if (isFrozen && !l.isTestLock()) {
|
||||
return ResponseEntity.status(409).body(null); // Frozen
|
||||
}
|
||||
|
||||
TimeLockService service = timeLockServiceFactory.create(l);
|
||||
service.unlock(l.getUnlockCode());
|
||||
|
||||
// Clean up verifications
|
||||
var verifications = verificationRepository.findByLockId(lockId);
|
||||
verifications.forEach(v -> verificationVoteRepository.deleteAllByVerificationId(v.getDisplayId()));
|
||||
verificationRepository.deleteAll(verifications);
|
||||
invitationRepository.deleteByLockId(lockId);
|
||||
timeLockRepository.deleteById(lockId);
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
// ── Notfall-Entsperrung ───────────────────────────────────────────────────────
|
||||
|
||||
@PostMapping("/timelock/{lockId}/emergency-unlock")
|
||||
@Transactional
|
||||
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 = timeLockRepository.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) {
|
||||
l.setEmergencyAutoUnlocked(true);
|
||||
l.setKeyholderRequestedUnlock(true);
|
||||
} else {
|
||||
systemMessageService.send(myId, l.getKeyholder(),
|
||||
"⚠️ NOTFALL: " + me.getName() + " bittet dringend um Freigabe des Locks. Bitte reagiere innerhalb einer Stunde.",
|
||||
"/keyholder.html", de.oaa.xxx.social.entity.MessageCause.EMERGENCY);
|
||||
}
|
||||
timeLockRepository.save(l);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
// ── Hilfsmethoden ─────────────────────────────────────────────────────────────
|
||||
|
||||
private TimeLockEntity buildBaseEntity(TimeLockTemplateEntity template, UUID keyholder, UUID lockee, boolean testLock) {
|
||||
TimeLockEntity lock = new TimeLockEntity();
|
||||
lock.setName(template.getName());
|
||||
lock.setLockee(lockee);
|
||||
lock.setKeyholder(keyholder);
|
||||
lock.setRequiresVerification(template.isRequiresVerification());
|
||||
lock.setTestLock(testLock);
|
||||
lock.setEndTimeVisible(template.isEndTimeVisible());
|
||||
lock.setTasks(template.getTasks());
|
||||
lock.setTaskMode(template.getTaskCardMode());
|
||||
lock.setTaskEveryMinutes(template.getTaskEveryMinutes());
|
||||
lock.setMinTasksPerDay(template.getMinTasksPerDay());
|
||||
lock.setSpinningWheelEntries(template.getSpinningWheelEntries());
|
||||
lock.setSpinsEveryMinutes(template.getSpinsEveryMinutes());
|
||||
lock.setMinSpinsPerDay(template.getMinSpinsPerDay());
|
||||
lock.setHygineOpeningDurationMinutes(template.getHygineOpeningDurationMinutes());
|
||||
lock.setHygineOpeningEveryMinites(template.getHygineOpeningEveryMinites());
|
||||
lock.setPenaltyType(template.getPenaltyType());
|
||||
lock.setPenaltyValue(template.getPenaltyValue());
|
||||
lock.setMinTimeInMinutes(template.getMinTimeInMinutes());
|
||||
lock.setMaxTimeInMinutes(template.getMaxTimeInMinutes());
|
||||
return lock;
|
||||
}
|
||||
|
||||
|
||||
private byte[] scaleImage(byte[] input, int maxSize) throws IOException {
|
||||
BufferedImage original = ImageIO.read(new ByteArrayInputStream(input));
|
||||
if (original == null) return input;
|
||||
int w = original.getWidth(), h = original.getHeight();
|
||||
if (w <= maxSize && h <= maxSize) return input;
|
||||
double scale = (double) maxSize / Math.max(w, h);
|
||||
int newW = (int) (w * scale), newH = (int) (h * scale);
|
||||
BufferedImage scaled = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g = scaled.createGraphics();
|
||||
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g.drawImage(original, 0, 0, newW, newH, null);
|
||||
g.dispose();
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ImageIO.write(scaled, "jpeg", out);
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -3,115 +3,60 @@ package de.oaa.xxx.games.chastity.timelock;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.oaa.xxx.games.chastity.common.BaseLockEntity;
|
||||
import de.oaa.xxx.games.chastity.common.PenaltyType;
|
||||
import de.oaa.xxx.games.chastity.spinningwheel.SpinningWheelConverter;
|
||||
import de.oaa.xxx.games.chastity.spinningwheel.SpinningWheelEntry;
|
||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||
import de.oaa.xxx.games.chastity.tasks.TaskListConverter;
|
||||
import de.oaa.xxx.games.chastity.tasks.TaskMode;
|
||||
import de.oaa.xxx.games.chastity.unlock.TempOpeningReason;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Convert;
|
||||
import jakarta.persistence.DiscriminatorValue;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "time_lock")
|
||||
public class TimeLockEntity {
|
||||
@DiscriminatorValue("TIMELOCK")
|
||||
public class TimeLockEntity extends BaseLockEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
@Column
|
||||
private UUID lockId;
|
||||
@Column
|
||||
private String name;
|
||||
// Settings
|
||||
@Column(nullable = false)
|
||||
private UUID lockee;
|
||||
@Column
|
||||
private UUID keyholder;
|
||||
@Column
|
||||
private LocalDateTime startTime;
|
||||
@Column
|
||||
private LocalDateTime unlockTime;
|
||||
@Column
|
||||
private boolean endTimeVisible;
|
||||
@Convert(converter = TaskListConverter.class)
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private List<Task> tasks;
|
||||
@Column
|
||||
private Integer minTimeInMinutes;
|
||||
@Column
|
||||
private Integer maxTimeInMinutes;
|
||||
|
||||
@Column
|
||||
private Integer taskEveryMinutes;
|
||||
@Column
|
||||
private Integer minTasksPerDay;
|
||||
|
||||
@Convert(converter = SpinningWheelConverter.class)
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private List<SpinningWheelEntry> spinningWheelEntries;
|
||||
@Column
|
||||
private Integer hygineOpeningDurationMinutes;
|
||||
@Column
|
||||
private Integer hygineOpeningEveryMinites;
|
||||
@Column
|
||||
private boolean requiresVerification;
|
||||
@Column
|
||||
private boolean testLock;
|
||||
@Column
|
||||
private Integer unlockCodeLength;
|
||||
@Column(nullable = false)
|
||||
private TaskMode taskMode = TaskMode.RANDOM;
|
||||
@Column
|
||||
private Integer spinsEveryMinutes;
|
||||
@Column
|
||||
private Integer minSpinsPerDay;
|
||||
|
||||
@Column
|
||||
private PenaltyType penaltyType;
|
||||
@Column
|
||||
private Integer penaltyValue;
|
||||
|
||||
@Column
|
||||
private LocalDateTime lastHygineOpening;
|
||||
@Column
|
||||
private LocalDateTime tempOpeningTime; // If null, not while hygine opening
|
||||
@Column
|
||||
private Integer tempOpeningDuration;
|
||||
@Column
|
||||
private TempOpeningReason tempOpeningReason;
|
||||
@Column
|
||||
private LocalDateTime frozenFrom;
|
||||
@Column
|
||||
private LocalDateTime frozenUntil;
|
||||
@Column
|
||||
private String currentTask;
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String currentTaskDescription;
|
||||
@Column
|
||||
private LocalDateTime taskUntil;
|
||||
@Column
|
||||
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;
|
||||
|
||||
@Column
|
||||
private List<LocalDateTime> taskTimes;
|
||||
@Column
|
||||
private List<LocalDateTime> spinningWheelTimes;
|
||||
@Column
|
||||
private LocalDate lastCheck;
|
||||
|
||||
public TaskMode getTaskMode() { return taskMode != null ? taskMode : TaskMode.RANDOM; }
|
||||
|
||||
}
|
||||
|
||||
@@ -6,4 +6,6 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface TimeLockRepository extends JpaRepository<TimeLockEntity, UUID> {
|
||||
|
||||
boolean existsByLockeeAndStartTimeIsNotNullAndUnlockTimeIsNull(UUID lockee);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,106 +1,164 @@
|
||||
package de.oaa.xxx.games.chastity.timelock;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import de.oaa.xxx.games.chastity.common.AbstractLockService;
|
||||
import de.oaa.xxx.games.chastity.common.BaseLockEntity;
|
||||
import de.oaa.xxx.games.chastity.common.BaseLockService;
|
||||
import de.oaa.xxx.games.chastity.common.CodeCreator;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationEntity;
|
||||
import de.oaa.xxx.games.chastity.common.VerificationCommonDTO;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityPilloryEntity;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityPilloryReason;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityPilloryRepository;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityTaskVoteRepository;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationEntity;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationVoteRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderTaskChoiceRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderVerificationEntity;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderVerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.lockcontroll.LockControl;
|
||||
import de.oaa.xxx.games.chastity.lockcontroll.LockControlCallback;
|
||||
import de.oaa.xxx.games.chastity.lockcontroll.TTLockControl;
|
||||
import de.oaa.xxx.games.chastity.lockcontroll.TrustLockControl;
|
||||
import de.oaa.xxx.games.chastity.lockcontroll.UnlockcodeLockControl;
|
||||
import de.oaa.xxx.games.chastity.spinningwheel.SpinningWheelEntry;
|
||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||
import de.oaa.xxx.games.chastity.tasks.TaskMode;
|
||||
import de.oaa.xxx.games.chastity.unlock.TempOpeningReason;
|
||||
import de.oaa.xxx.games.chastity.unlock.UnlockCodeHistoryService;
|
||||
import de.oaa.xxx.games.chastity.verification.VerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.verification.VerificationVoteRepository;
|
||||
import de.oaa.xxx.games.history.GameHistoryEntity;
|
||||
import de.oaa.xxx.games.history.GameHistoryRepository;
|
||||
import de.oaa.xxx.games.history.GameType;
|
||||
import de.oaa.xxx.social.SystemMessageService;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
|
||||
public class TimeLockService extends AbstractLockService implements LockControlCallback {
|
||||
public class TimeLockService extends BaseLockService implements LockControlCallback {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TimeLockService.class);
|
||||
private final TimeLockEntity lock;
|
||||
private final TimeLockRepository timeLockRepository;
|
||||
private final VerificationRepository verificationRepository;
|
||||
private final GameHistoryRepository gameHistoryRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final KeyholderNotificationRepository keyholderNotificationRepository;
|
||||
private final UnlockCodeHistoryService unlockCodeHistoryService;
|
||||
private final CommunityPilloryRepository pilloryRepository;
|
||||
|
||||
private LockControl lockControl;
|
||||
|
||||
public TimeLockService(TimeLockEntity lock, VerificationRepository verificationRepository,
|
||||
VerificationVoteRepository verificationVoteRepository,
|
||||
public TimeLockService(TimeLockEntity lock,
|
||||
CommunityVerificationRepository verificationRepository,
|
||||
CommunityVerificationVoteRepository verificationVoteRepository,
|
||||
TimeLockRepository timeLockRepository,
|
||||
GameHistoryRepository gameHistoryRepository,
|
||||
UserRepository userRepository,
|
||||
KeyholderNotificationRepository keyholderNotificationRepository,
|
||||
UnlockCodeHistoryService unlockCodeHistoryService) {
|
||||
super(verificationVoteRepository);
|
||||
KeyholderTaskChoiceRepository keyholderTaskChoiceRepository,
|
||||
KeyholderVerificationRepository keyholderVerificationRepository,
|
||||
CommunityTaskVoteRepository communityTaskVoteRepository,
|
||||
CommunityPilloryRepository pilloryRepository,
|
||||
UnlockCodeHistoryService unlockCodeHistoryService,
|
||||
SystemMessageService systemMessageService) {
|
||||
super(verificationVoteRepository, verificationRepository, keyholderVerificationRepository,
|
||||
gameHistoryRepository, userRepository, keyholderNotificationRepository,
|
||||
systemMessageService, unlockCodeHistoryService,
|
||||
keyholderTaskChoiceRepository, communityTaskVoteRepository);
|
||||
this.lock = lock;
|
||||
this.timeLockRepository = timeLockRepository;
|
||||
this.verificationRepository = verificationRepository;
|
||||
this.gameHistoryRepository = gameHistoryRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.keyholderNotificationRepository = keyholderNotificationRepository;
|
||||
this.unlockCodeHistoryService = unlockCodeHistoryService;
|
||||
this.pilloryRepository = pilloryRepository;
|
||||
}
|
||||
|
||||
public void init(TimeLockTemplate template, TimeLockAdditionalSettings settings) {
|
||||
// ── Abstract method implementations ──────────────────────────────────────
|
||||
|
||||
@Override
|
||||
protected BaseLockEntity getLock() {
|
||||
return lock;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveLock() {
|
||||
timeLockRepository.save(lock);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GameType getGameType() {
|
||||
return GameType.TIMELOCK;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyHygieneOvertime(Long overtime) {
|
||||
lock.setUnlockTime(lock.getUnlockTime().plusMinutes(overtime * 4));
|
||||
}
|
||||
|
||||
// ── Hook overrides ────────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
protected void beforePhysicalUnlock() {
|
||||
lockControl.unlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterHygieneClosing() {
|
||||
lockControl.lock();
|
||||
}
|
||||
|
||||
// ── Initialisation ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Initialisiert ein neues Lock anhand eines Template und Laufzeit-Einstellungen.
|
||||
* Ruft am Ende lockControl.lock() auf – bei UNLOCK_CODE wird dabei der Entsperrcode
|
||||
* generiert und das Lock bereits persistiert.
|
||||
*/
|
||||
public void init(TimeLockTemplateEntity template, TimeLockAdditionalSettings settings) {
|
||||
switch (settings.controllType()) {
|
||||
case TTLOCK -> lockControl = new TTLockControl();
|
||||
case TRUST -> lockControl = new TrustLockControl();
|
||||
case UNLOCK_CODE -> lockControl = new UnlockcodeLockControl(this);
|
||||
}
|
||||
|
||||
lock.setLockee(UUID.randomUUID());
|
||||
lock.setName(template.name());
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
lock.setStartTime(now);
|
||||
lock.setName(template.getName());
|
||||
lock.setLockee(settings.lockee());
|
||||
lock.setKeyholder(settings.keyholder());
|
||||
lock.setRequiresVerification(template.requiresVerification());
|
||||
lock.setRequiresVerification(template.isRequiresVerification());
|
||||
lock.setTestLock(settings.testlock());
|
||||
lock.setUnlockCodeLength(settings.unlockCodeLength());
|
||||
lock.setUnlockCodeLength(settings.unlockCodeLength() != null ? settings.unlockCodeLength() : 5);
|
||||
|
||||
lock.setStartTime(LocalDateTime.now());
|
||||
Integer unlockTimeMinutes = template.maxTimeInMinutes();
|
||||
if (template.minTimeInMinutes() != null) {
|
||||
unlockTimeMinutes = new Random().nextInt(template.minTimeInMinutes(), template.maxTimeInMinutes());
|
||||
Integer minMinutes = template.getMinTimeInMinutes();
|
||||
Integer maxMinutes = template.getMaxTimeInMinutes() != null ? template.getMaxTimeInMinutes() : 60;
|
||||
int unlockTimeMinutes = (minMinutes != null && minMinutes < maxMinutes)
|
||||
? minMinutes + new Random().nextInt(maxMinutes - minMinutes)
|
||||
: maxMinutes;
|
||||
lock.setUnlockTime(now.plusMinutes(unlockTimeMinutes));
|
||||
lock.setEndTimeVisible(template.isEndTimeVisible());
|
||||
|
||||
lock.setTasks(template.getTasks());
|
||||
lock.setTaskEveryMinutes(template.getTaskEveryMinutes());
|
||||
lock.setMinTasksPerDay(template.getMinTasksPerDay());
|
||||
|
||||
lock.setSpinningWheelEntries(template.getSpinningWheelEntries());
|
||||
lock.setSpinsEveryMinutes(template.getSpinsEveryMinutes());
|
||||
lock.setMinSpinsPerDay(template.getMinSpinsPerDay());
|
||||
|
||||
lock.setHygineOpeningDurationMinutes(template.getHygineOpeningDurationMinutes());
|
||||
lock.setHygineOpeningEveryMinites(template.getHygineOpeningEveryMinites());
|
||||
if (template.getHygineOpeningEveryMinites() != null) {
|
||||
lock.setLastHygineOpening(now);
|
||||
}
|
||||
lock.setUnlockTime(LocalDateTime.now().plusMinutes(unlockTimeMinutes));
|
||||
lock.setEndTimeVisible(template.endTimeVisible());
|
||||
|
||||
lock.setTasks(template.tasks());
|
||||
lock.setTaskEveryMinutes(template.taskEveryMinutes());
|
||||
lock.setMinTasksPerDay(template.minTasksPerDay());
|
||||
|
||||
lock.setSpinningWheelEntries(template.spinningWheelEntries());
|
||||
lock.setSpinsEveryMinutes(template.spinsEveryMinutes());
|
||||
lock.setMinSpinsPerDay(template.minSpinsPerDay());
|
||||
|
||||
lock.setHygineOpeningDurationMinutes(template.hygineOpeningDurationMinutes());
|
||||
lock.setHygineOpeningEveryMinites(template.hygineOpeningEveryMinites());
|
||||
|
||||
lock.setTaskMode(template.taskMode());
|
||||
lock.setTaskMode(template.getTaskCardMode());
|
||||
lock.setPenaltyType(template.getPenaltyType());
|
||||
lock.setPenaltyValue(template.getPenaltyValue());
|
||||
lock.setMinTimeInMinutes(template.getMinTimeInMinutes());
|
||||
lock.setMaxTimeInMinutes(template.getMaxTimeInMinutes());
|
||||
|
||||
lockControl.lock();
|
||||
}
|
||||
|
||||
// ── Spinning wheel ────────────────────────────────────────────────────────
|
||||
|
||||
public SpinningWheelEntry spinWheel() {
|
||||
if (TempOpeningReason.HYGIENE != lock.getTempOpeningReason()) {
|
||||
var entries = lock.getSpinningWheelEntries();
|
||||
@@ -108,10 +166,12 @@ public class TimeLockService extends AbstractLockService implements LockControlC
|
||||
entry.getType().apply(this, entry.getIntVal(), entry.getStringVal());
|
||||
return entry;
|
||||
}
|
||||
// Nicht während der Hyhiene Öffnung
|
||||
// Nicht während der Hygiene-Öffnung
|
||||
return null;
|
||||
}
|
||||
|
||||
// ── Time controls ─────────────────────────────────────────────────────────
|
||||
|
||||
public void addTime(Integer intVal) {
|
||||
LOGGER.debug("Lock addTime: %s minutes", intVal);
|
||||
lock.setUnlockTime(lock.getUnlockTime().plusMinutes(intVal));
|
||||
@@ -144,40 +204,26 @@ public class TimeLockService extends AbstractLockService implements LockControlC
|
||||
}
|
||||
}
|
||||
|
||||
public void testUnfreeze() {
|
||||
if (lock.getFrozenUntil().isAfter(LocalDateTime.now())) {
|
||||
unfreeze();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tasks ─────────────────────────────────────────────────────────────────
|
||||
|
||||
public void task() {
|
||||
if (TempOpeningReason.HYGIENE != lock.getTempOpeningReason()) {
|
||||
switch (lock.getTaskMode()) {
|
||||
case TaskMode.RANDOM -> applyRandomTask();
|
||||
case TaskMode.KEYHOLDER -> startKeyHolderVote();
|
||||
case TaskMode.COMMUNITY -> startCommunityVode();
|
||||
case TaskMode.KEYHOLDER -> startKeyholderVote();
|
||||
case TaskMode.COMMUNITY -> {
|
||||
if (lock.isTestLock()) applyRandomTask();
|
||||
else startCommunityVote();
|
||||
}
|
||||
}
|
||||
// Nicht während der Hyhiene Öffnung
|
||||
}
|
||||
|
||||
private void startKeyHolderVote() {
|
||||
// Keyholder Vote starten
|
||||
}
|
||||
|
||||
private void startCommunityVode() {
|
||||
// Community Vote starten
|
||||
}
|
||||
|
||||
public void applyRandomTask() {
|
||||
LOGGER.debug("Apply random task");
|
||||
var tasks = lock.getTasks();
|
||||
if (!tasks.isEmpty()) {
|
||||
task(tasks.get(new Random().nextInt(tasks.size())));
|
||||
}
|
||||
}
|
||||
|
||||
public void task(Task task) {
|
||||
LOGGER.debug("Apply task {}", task);
|
||||
lock.setCurrentTask(task.resolveTitle());
|
||||
lock.setCurrentTaskDescription(task.getDescription());
|
||||
if (task.getMinutes() != null && task.getMinutes() > 0) {
|
||||
lock.setTaskUntil(LocalDateTime.now().plusMinutes(task.getMinutes()));
|
||||
}
|
||||
// Nicht während der Hygiene-Öffnung
|
||||
}
|
||||
|
||||
public void text(Integer intVal, String stringVal) {
|
||||
@@ -188,88 +234,80 @@ public class TimeLockService extends AbstractLockService implements LockControlC
|
||||
}
|
||||
}
|
||||
|
||||
public String clearTask() {
|
||||
LOGGER.debug("Clear task");
|
||||
lock.setCurrentTask(null);
|
||||
lock.setCurrentTaskDescription(null);
|
||||
lock.setTaskUntil(null);
|
||||
return "";
|
||||
}
|
||||
// ── Verification ──────────────────────────────────────────────────────────
|
||||
|
||||
public void testUnfreeze() {
|
||||
if (lock.getFrozenUntil().isAfter(LocalDateTime.now())) {
|
||||
unfreeze();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Gibt eine bestehende Verifikation für heute zurück (Idempotenz) oder legt eine neue an.
|
||||
* Erstellt je nach Keyholder-Präsenz eine KeyholderVerification oder CommunityVerification.
|
||||
*/
|
||||
public VerificationCommonDTO startVerification() {
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDateTime todayStart = today.atStartOfDay();
|
||||
LocalDateTime todayEnd = todayStart.plusDays(1);
|
||||
|
||||
public void unlock(String unlockCode) {
|
||||
lockControl.unlock();
|
||||
this.lock.setUnlockTime(LocalDateTime.now());
|
||||
boolean valid = true;
|
||||
if (lock.isEmergencyAutoUnlocked()) {
|
||||
valid = false;
|
||||
LOGGER.debug("Lock invalid - Emergency Auto-Unlock (1h timer)");
|
||||
}
|
||||
if (lock.isTestLock()) {
|
||||
valid = false;
|
||||
} else if (Duration.between(lock.getStartTime(), lock.getUnlockTime()).toHours() > 24) {
|
||||
Set<LocalDate> verifications = verificationRepository.findByLockId(this.lock.getLockId()).stream()
|
||||
.filter(verification -> isValid(verification))
|
||||
.map(verification -> verification.getVerificationTime().toLocalDate()).collect(Collectors.toSet());
|
||||
|
||||
LocalDate current = this.lock.getStartTime().toLocalDate();
|
||||
LocalDate last = this.lock.getUnlockTime().toLocalDate().minusDays(1);
|
||||
|
||||
while (!current.isAfter(last)) {
|
||||
if (!verifications.contains(current)) {
|
||||
valid = false;
|
||||
LOGGER.debug("Lock invalid - no daily verification on %s", current.toString());
|
||||
break;
|
||||
}
|
||||
current = current.plusDays(1);
|
||||
}
|
||||
}
|
||||
|
||||
lock.setUnlockTime(LocalDateTime.now());
|
||||
LOGGER.debug("Unlocked at {}", lock.getUnlockTime());
|
||||
timeLockRepository.save(lock);
|
||||
|
||||
if (valid) {
|
||||
long durationMinutes = Duration.between(lock.getStartTime(), lock.getUnlockTime()).toMinutes();
|
||||
|
||||
// Gemeinsamer History-Eintrag mit Teilnehmerliste
|
||||
GameHistoryEntity entry = new GameHistoryEntity();
|
||||
entry.setGameType(de.oaa.xxx.games.history.GameType.CARDLOCK);
|
||||
entry.setGameName(lock.getName());
|
||||
entry.setStartTime(lock.getStartTime());
|
||||
entry.setEndTime(lock.getUnlockTime());
|
||||
entry.setDurationMinutes(durationMinutes);
|
||||
entry.addParticipant(lock.getLockee(), de.oaa.xxx.games.history.GameRole.LOCKEE);
|
||||
if (lock.getKeyholder() != null) {
|
||||
entry.addParticipant(lock.getKeyholder(), de.oaa.xxx.games.history.GameRole.KEYHOLDER);
|
||||
}
|
||||
gameHistoryRepository.save(entry);
|
||||
var existing = keyholderVerificationRepository.findByLockId(lock.getLockId()).stream()
|
||||
.filter(v -> v.getCreatedAt().toLocalDate().equals(today))
|
||||
.findFirst();
|
||||
if (existing.isPresent()) return existing.get().toCommonVerification();
|
||||
|
||||
int minutes = (int) durationMinutes;
|
||||
userRepository.findById(lock.getLockee()).ifPresent(u -> {
|
||||
u.setLockeeXp(u.getLockeeXp() + minutes);
|
||||
userRepository.save(u);
|
||||
});
|
||||
KeyholderVerificationEntity v = new KeyholderVerificationEntity();
|
||||
v.setId(UUID.randomUUID());
|
||||
v.setLockId(lock.getLockId());
|
||||
v.setLockeeId(lock.getLockee());
|
||||
v.setKeyholderId(lock.getKeyholder());
|
||||
v.setCode(CodeCreator.createAlphanumeric(6));
|
||||
v.setCreatedAt(LocalDateTime.now());
|
||||
keyholderVerificationRepository.save(v);
|
||||
return v.toCommonVerification();
|
||||
} else {
|
||||
var pending = communityVerificationRepository
|
||||
.findByLockIdAndCreatedAtBetweenAndImageIsNull(lock.getLockId(), todayStart, todayEnd);
|
||||
if (!pending.isEmpty()) return pending.get(0).toCommonVerification();
|
||||
var completed = communityVerificationRepository
|
||||
.findByLockIdAndCreatedAtBetweenAndImageIsNotNull(lock.getLockId(), todayStart, todayEnd);
|
||||
if (!completed.isEmpty()) return completed.get(0).toCommonVerification();
|
||||
|
||||
CommunityVerificationEntity v = new CommunityVerificationEntity();
|
||||
v.setId(UUID.randomUUID());
|
||||
v.setLockId(lock.getLockId());
|
||||
v.setLockeeId(lock.getLockee());
|
||||
v.setCode(CodeCreator.createAlphanumeric(6));
|
||||
v.setCreatedAt(LocalDateTime.now());
|
||||
communityVerificationRepository.save(v);
|
||||
return v.toCommonVerification();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Speichert das Verifikationsbild auf der richtigen Entity (Keyholder oder Community).
|
||||
* Gibt false zurück wenn die Verifikation nicht gefunden wurde oder nicht zu diesem Lock gehört.
|
||||
*/
|
||||
public boolean completeVerification(UUID verificationId, byte[] image) {
|
||||
if (lock.getKeyholder() != null) {
|
||||
userRepository.findById(lock.getKeyholder()).ifPresent(u -> {
|
||||
u.setKeyholderXp(u.getKeyholderXp() + minutes);
|
||||
userRepository.save(u);
|
||||
});
|
||||
}
|
||||
var vOpt = keyholderVerificationRepository.findById(verificationId);
|
||||
if (vOpt.isEmpty() || !vOpt.get().getLockId().equals(lock.getLockId())) return false;
|
||||
var v = vOpt.get();
|
||||
v.setImage(image);
|
||||
keyholderVerificationRepository.save(v);
|
||||
} else {
|
||||
var vOpt = communityVerificationRepository.findById(verificationId);
|
||||
if (vOpt.isEmpty() || !vOpt.get().getLockId().equals(lock.getLockId())) return false;
|
||||
var v = vOpt.get();
|
||||
v.setImage(image);
|
||||
communityVerificationRepository.save(v);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── Penalty & check ───────────────────────────────────────────────────────
|
||||
|
||||
public void applyPenalty() {
|
||||
if (lock.getPenaltyType() != null) {
|
||||
switch (lock.getPenaltyType()) {
|
||||
case ADD -> addTime(lock.getPenaltyValue());
|
||||
case FREEZE -> freeze();
|
||||
case PILLORY -> pillory();
|
||||
case PILLORY -> pillory(CommunityPilloryReason.HYGIENE_OPENING_EXEEDED, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -288,8 +326,8 @@ public class TimeLockService extends AbstractLockService implements LockControlC
|
||||
}
|
||||
}
|
||||
if (lock.getMinSpinsPerDay() != null) {
|
||||
if (lock.getMinSpinsPerDay() > lock.getSpinningWheelTimes().stream().map(LocalDateTime::toLocalDate)
|
||||
.filter(yesterday::equals).count()) {
|
||||
if (lock.getMinSpinsPerDay() > lock.getSpinningWheelTimes().stream()
|
||||
.map(LocalDateTime::toLocalDate).filter(yesterday::equals).count()) {
|
||||
violation = true;
|
||||
}
|
||||
}
|
||||
@@ -302,10 +340,25 @@ public class TimeLockService extends AbstractLockService implements LockControlC
|
||||
}
|
||||
}
|
||||
|
||||
public void pillory() {
|
||||
// TODO an den Pranger stellen
|
||||
public void pillory(CommunityPilloryReason reason, UUID keyholderId) {
|
||||
CommunityPilloryEntity pillory = new CommunityPilloryEntity();
|
||||
pillory.setCreatedAt(LocalDateTime.now());
|
||||
pillory.setLockeeId(lock.getLockee());
|
||||
pillory.setLockId(lock.getLockId());
|
||||
pillory.setReason(reason);
|
||||
pillory.setKeyholderId(keyholderId);
|
||||
pilloryRepository.save(pillory);
|
||||
}
|
||||
|
||||
// ── Hygiene opening ───────────────────────────────────────────────────────
|
||||
|
||||
public void startHygieneOpening() {
|
||||
lockControl.unlock();
|
||||
startTempOpening(TempOpeningReason.HYGIENE, lock.getHygineOpeningDurationMinutes());
|
||||
}
|
||||
|
||||
// ── LockControlCallback ───────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public void setUnlockCode(String code) {
|
||||
lock.setUnlockCode(code);
|
||||
@@ -316,66 +369,4 @@ public class TimeLockService extends AbstractLockService implements LockControlC
|
||||
public int getUnlockcodeLenght() {
|
||||
return lock.getUnlockCodeLength();
|
||||
}
|
||||
|
||||
|
||||
public void startHygieneOpening() {
|
||||
tempOperning(TempOpeningReason.HYGIENE, lock.getHygineOpeningDurationMinutes());
|
||||
}
|
||||
|
||||
private Long calcOvertime() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
Long overtime = null;
|
||||
if (lock.getTempOpeningTime() != null && lock.getTempOpeningDuration() != null) {
|
||||
LocalDateTime dueTime = lock.getTempOpeningTime().plusMinutes(lock.getTempOpeningDuration());
|
||||
if (LocalDateTime.now().isAfter(dueTime)) {
|
||||
overtime = ChronoUnit.MINUTES.between(dueTime, now);
|
||||
}
|
||||
}
|
||||
return overtime;
|
||||
}
|
||||
|
||||
public String endHygieneOpening() {
|
||||
lockControl.lock();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
Long overtime = calcOvertime();
|
||||
if (overtime != null) {
|
||||
if (lock.getKeyholder() != null) {
|
||||
reportKeyholder(overtime);
|
||||
}
|
||||
addOvertime(overtime);
|
||||
}
|
||||
lock.setLastHygineOpening(now);
|
||||
lock.setTempOpeningDuration(null);
|
||||
lock.setTempOpeningTime(null);
|
||||
|
||||
var code = CodeCreator.createAlphanumericCode(lock.getUnlockCodeLength());
|
||||
lock.setUnlockCode(code);
|
||||
timeLockRepository.save(lock);
|
||||
return code;
|
||||
}
|
||||
|
||||
private void reportKeyholder(Long overtime) {
|
||||
KeyholderNotificationEntity notification = new KeyholderNotificationEntity();
|
||||
notification.setLockId(lock.getLockId());
|
||||
notification.setLockeeId(lock.getLockee());
|
||||
notification.setKeyholderUserId(lock.getKeyholder());
|
||||
notification.setViolationTime(LocalDateTime.now());
|
||||
notification.setOvertimeMinutes(overtime);
|
||||
keyholderNotificationRepository.save(notification);
|
||||
}
|
||||
|
||||
private void addOvertime(Long overtime) {
|
||||
lock.setUnlockTime(lock.getUnlockTime().plusMinutes(overtime * 4));
|
||||
}
|
||||
|
||||
private void tempOperning(TempOpeningReason reason, Integer duration) {
|
||||
assert duration != null;
|
||||
lockControl.unlock();
|
||||
lock.setTempOpeningReason(reason);
|
||||
lock.setTempOpeningTime(LocalDateTime.now());;
|
||||
lock.setTempOpeningDuration(duration);
|
||||
timeLockRepository.save(lock);
|
||||
unlockCodeHistoryService.save(lock.getLockee(), lock.getLockId(), lock.getName(), lock.getUnlockCode(), reason.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,53 +2,64 @@ package de.oaa.xxx.games.chastity.timelock;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import de.oaa.xxx.games.chastity.community.CommunityPilloryRepository;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityTaskVoteRepository;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.community.CommunityVerificationVoteRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderTaskChoiceRepository;
|
||||
import de.oaa.xxx.games.chastity.keyholder.KeyholderVerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.unlock.UnlockCodeHistoryService;
|
||||
import de.oaa.xxx.games.chastity.verification.VerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.verification.VerificationVoteRepository;
|
||||
import de.oaa.xxx.games.history.GameHistoryRepository;
|
||||
import de.oaa.xxx.social.SystemMessageService;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
|
||||
@Service
|
||||
public class TimeLockServiceFactory {
|
||||
|
||||
|
||||
private final VerificationRepository verificationRepository;
|
||||
private final VerificationVoteRepository verificationVoteRepository;
|
||||
private final TimeLockRepository timeLockRepository;
|
||||
private final CommunityVerificationRepository communityVerificationRepository;
|
||||
private final GameHistoryRepository gameHistoryRepository;
|
||||
private final UserRepository userRepository;
|
||||
private KeyholderNotificationRepository keyholderNotificationRepository;
|
||||
private final UnlockCodeHistoryService unlockCodeHistoryService;
|
||||
private final KeyholderNotificationRepository keyholderNotificationRepository;
|
||||
private final CommunityPilloryRepository pilloryRepository;
|
||||
private final CommunityTaskVoteRepository communityTaskVoteRepository;
|
||||
private final KeyholderTaskChoiceRepository keyholderTaskChoiceRepository;
|
||||
private final KeyholderVerificationRepository keyholderVerificationRepository;
|
||||
|
||||
public TimeLockServiceFactory(VerificationRepository verificationRepository,
|
||||
VerificationVoteRepository verificationVoteRepository,
|
||||
TimeLockRepository timeLockRepository,
|
||||
GameHistoryRepository gameHistoryRepository,
|
||||
UserRepository userRepository,
|
||||
private final UnlockCodeHistoryService unlockCodeHistoryService;
|
||||
private final SystemMessageService systemMessageService;
|
||||
private CommunityVerificationVoteRepository communityVerificationVoteRepository;
|
||||
|
||||
public TimeLockServiceFactory(CommunityVerificationRepository verificationRepository,
|
||||
CommunityVerificationVoteRepository verificationVoteRepository, TimeLockRepository timeLockRepository,
|
||||
GameHistoryRepository gameHistoryRepository, UserRepository userRepository,
|
||||
KeyholderNotificationRepository keyholderNotificationRepository,
|
||||
UnlockCodeHistoryService unlockCodeHistoryService) {
|
||||
this.verificationRepository = verificationRepository;
|
||||
this.verificationVoteRepository = verificationVoteRepository;
|
||||
KeyholderTaskChoiceRepository keyholderTaskChoiceRepository,
|
||||
KeyholderVerificationRepository keyholderVerificationRepository,
|
||||
CommunityTaskVoteRepository communityTaskVoteRepository, CommunityPilloryRepository pilloryRepository,
|
||||
UnlockCodeHistoryService unlockCodeHistoryService, SystemMessageService systemMessageService) {
|
||||
this.communityVerificationVoteRepository = verificationVoteRepository;
|
||||
this.timeLockRepository = timeLockRepository;
|
||||
this.communityVerificationRepository = verificationRepository;
|
||||
this.gameHistoryRepository = gameHistoryRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.keyholderNotificationRepository = keyholderNotificationRepository;
|
||||
this.pilloryRepository = pilloryRepository;
|
||||
this.unlockCodeHistoryService = unlockCodeHistoryService;
|
||||
this.systemMessageService = systemMessageService;
|
||||
this.keyholderTaskChoiceRepository = keyholderTaskChoiceRepository;
|
||||
this.communityTaskVoteRepository = communityTaskVoteRepository;
|
||||
this.keyholderVerificationRepository = keyholderVerificationRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine neue CardLockService-Instanz für das gegebene Lock.
|
||||
*/
|
||||
public TimeLockService create(TimeLockEntity lock) {
|
||||
return new TimeLockService(
|
||||
lock,
|
||||
verificationRepository,
|
||||
verificationVoteRepository,
|
||||
timeLockRepository,
|
||||
gameHistoryRepository,
|
||||
userRepository,
|
||||
keyholderNotificationRepository,
|
||||
unlockCodeHistoryService
|
||||
);
|
||||
return new TimeLockService(lock, communityVerificationRepository, communityVerificationVoteRepository,
|
||||
timeLockRepository, gameHistoryRepository, userRepository, keyholderNotificationRepository,
|
||||
keyholderTaskChoiceRepository, keyholderVerificationRepository, communityTaskVoteRepository,
|
||||
pilloryRepository, unlockCodeHistoryService, systemMessageService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
package de.oaa.xxx.games.chastity.timelock;
|
||||
|
||||
import de.oaa.xxx.games.chastity.common.PenaltyType;
|
||||
import de.oaa.xxx.games.chastity.spinningwheel.SpinningWheelEntry;
|
||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||
import de.oaa.xxx.games.chastity.tasks.TaskMode;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/timelock/templates")
|
||||
public class TimeLockTemplateController {
|
||||
|
||||
private final TimeLockTemplateRepository templateRepository;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public TimeLockTemplateController(TimeLockTemplateRepository templateRepository,
|
||||
UserRepository userRepository) {
|
||||
this.templateRepository = templateRepository;
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
record TemplateRequest(
|
||||
String name,
|
||||
Integer minTimeInMinutes,
|
||||
Integer maxTimeInMinutes,
|
||||
boolean endTimeVisible,
|
||||
Integer hygineOpeningDurationMinutes,
|
||||
Integer hygineOpeningEveryMinites,
|
||||
List<Task> tasks,
|
||||
Integer taskEveryMinutes,
|
||||
Integer minTasksPerDay,
|
||||
List<SpinningWheelEntry> spinningWheelEntries,
|
||||
Integer spinsEveryMinutes,
|
||||
Integer minSpinsPerDay,
|
||||
boolean requiresVerification,
|
||||
TaskMode taskMode,
|
||||
PenaltyType penaltyType,
|
||||
Integer penaltyValue
|
||||
) {}
|
||||
|
||||
private Map<String, Object> toDto(TimeLockTemplateEntity t) {
|
||||
Map<String, Object> dto = new LinkedHashMap<>();
|
||||
dto.put("templateId", t.getTemplateId());
|
||||
dto.put("name", t.getName());
|
||||
dto.put("minTimeInMinutes", t.getMinTimeInMinutes());
|
||||
dto.put("maxTimeInMinutes", t.getMaxTimeInMinutes());
|
||||
dto.put("endTimeVisible", t.isEndTimeVisible());
|
||||
dto.put("hygineOpeningEveryMinites", t.getHygineOpeningEveryMinites());
|
||||
dto.put("hygineOpeningDurationMinutes", t.getHygineOpeningDurationMinutes());
|
||||
dto.put("tasks", t.getTasks() != null ? t.getTasks() : List.of());
|
||||
dto.put("taskEveryMinutes", t.getTaskEveryMinutes());
|
||||
dto.put("minTasksPerDay", t.getMinTasksPerDay());
|
||||
dto.put("spinningWheelEntries", t.getSpinningWheelEntries() != null ? t.getSpinningWheelEntries() : List.of());
|
||||
dto.put("spinsEveryMinutes", t.getSpinsEveryMinutes());
|
||||
dto.put("minSpinsPerDay", t.getMinSpinsPerDay());
|
||||
dto.put("requiresVerification", t.isRequiresVerification());
|
||||
dto.put("taskMode", t.getTaskCardMode());
|
||||
dto.put("penaltyType", t.getPenaltyType());
|
||||
dto.put("penaltyValue", t.getPenaltyValue());
|
||||
return dto;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<Map<String, Object>>> list(Principal principal) {
|
||||
var meOpt = userRepository.findByEmail(principal.getName());
|
||||
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||
UUID myId = meOpt.get().getUserId();
|
||||
List<Map<String, Object>> result = templateRepository.findByOwner(myId)
|
||||
.stream().map(this::toDto).collect(Collectors.toList());
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<Map<String, Object>> create(@RequestBody TemplateRequest req, Principal principal) {
|
||||
var meOpt = userRepository.findByEmail(principal.getName());
|
||||
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||
UUID myId = meOpt.get().getUserId();
|
||||
|
||||
if (req.name() == null || req.name().isBlank()) return ResponseEntity.badRequest().build();
|
||||
if (req.maxTimeInMinutes() == null || req.maxTimeInMinutes() < 1) return ResponseEntity.badRequest().build();
|
||||
|
||||
TimeLockTemplateEntity t = new TimeLockTemplateEntity();
|
||||
t.setOwner(myId);
|
||||
applyRequest(t, req);
|
||||
templateRepository.save(t);
|
||||
return ResponseEntity.ok(toDto(t));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<Map<String, Object>> update(@PathVariable UUID id,
|
||||
@RequestBody TemplateRequest req,
|
||||
Principal principal) {
|
||||
var meOpt = userRepository.findByEmail(principal.getName());
|
||||
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||
UUID myId = meOpt.get().getUserId();
|
||||
|
||||
var opt = templateRepository.findById(id);
|
||||
if (opt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
TimeLockTemplateEntity t = opt.get();
|
||||
if (!t.getOwner().equals(myId)) return ResponseEntity.status(403).build();
|
||||
|
||||
if (req.name() == null || req.name().isBlank()) return ResponseEntity.badRequest().build();
|
||||
if (req.maxTimeInMinutes() == null || req.maxTimeInMinutes() < 1) return ResponseEntity.badRequest().build();
|
||||
|
||||
applyRequest(t, req);
|
||||
templateRepository.save(t);
|
||||
return ResponseEntity.ok(toDto(t));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> delete(@PathVariable UUID id, Principal principal) {
|
||||
var meOpt = userRepository.findByEmail(principal.getName());
|
||||
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||
UUID myId = meOpt.get().getUserId();
|
||||
|
||||
var opt = templateRepository.findById(id);
|
||||
if (opt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
if (!opt.get().getOwner().equals(myId)) return ResponseEntity.status(403).build();
|
||||
|
||||
templateRepository.deleteById(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
private void applyRequest(TimeLockTemplateEntity t, TemplateRequest req) {
|
||||
t.setName(req.name());
|
||||
t.setMinTimeInMinutes(req.minTimeInMinutes());
|
||||
t.setMaxTimeInMinutes(req.maxTimeInMinutes());
|
||||
t.setEndTimeVisible(req.endTimeVisible());
|
||||
t.setHygineOpeningEveryMinites(req.hygineOpeningEveryMinites());
|
||||
t.setHygineOpeningDurationMinutes(req.hygineOpeningDurationMinutes());
|
||||
t.setTasks(req.tasks() != null ? req.tasks() : List.of());
|
||||
t.setTaskEveryMinutes(req.taskEveryMinutes());
|
||||
t.setMinTasksPerDay(req.minTasksPerDay());
|
||||
t.setSpinningWheelEntries(req.spinningWheelEntries() != null ? req.spinningWheelEntries() : List.of());
|
||||
t.setSpinsEveryMinutes(req.spinsEveryMinutes());
|
||||
t.setMinSpinsPerDay(req.minSpinsPerDay());
|
||||
t.setRequiresVerification(req.requiresVerification());
|
||||
t.setTaskMode(req.taskMode() != null ? req.taskMode() : TaskMode.RANDOM);
|
||||
t.setPenaltyType(req.penaltyType());
|
||||
t.setPenaltyValue(req.penaltyValue());
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,25 @@
|
||||
package de.oaa.xxx.games.chastity.timelock;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.oaa.xxx.games.chastity.common.BaseLockTemplateEntity;
|
||||
import de.oaa.xxx.games.chastity.common.PenaltyType;
|
||||
import de.oaa.xxx.games.chastity.spinningwheel.SpinningWheelConverter;
|
||||
import de.oaa.xxx.games.chastity.spinningwheel.SpinningWheelEntry;
|
||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||
import de.oaa.xxx.games.chastity.tasks.TaskListConverter;
|
||||
import de.oaa.xxx.games.chastity.tasks.TaskMode;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Convert;
|
||||
import jakarta.persistence.DiscriminatorValue;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "timelock_template")
|
||||
public class TimeLockTemplateEntity {
|
||||
@DiscriminatorValue("TIMELOCK")
|
||||
public class TimeLockTemplateEntity extends BaseLockTemplateEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
@Column
|
||||
private UUID templateId;
|
||||
@Column(nullable = false)
|
||||
private UUID owner;
|
||||
@Column
|
||||
private String name;
|
||||
@Column
|
||||
private Integer minTimeInMinutes;
|
||||
@Column
|
||||
@@ -40,18 +27,9 @@ public class TimeLockTemplateEntity {
|
||||
@Column
|
||||
private boolean endTimeVisible;
|
||||
@Column
|
||||
private Integer hygineOpeningDurationMinutes;
|
||||
@Column
|
||||
private Integer hygineOpeningEveryMinites;
|
||||
|
||||
@Convert(converter = TaskListConverter.class)
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private List<Task> tasks;
|
||||
@Column
|
||||
private Integer taskEveryMinutes;
|
||||
@Column
|
||||
private Integer minTasksPerDay;
|
||||
|
||||
@Convert(converter = SpinningWheelConverter.class)
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private List<SpinningWheelEntry> spinningWheelEntries;
|
||||
@@ -59,24 +37,15 @@ public class TimeLockTemplateEntity {
|
||||
private Integer spinsEveryMinutes;
|
||||
@Column
|
||||
private Integer minSpinsPerDay;
|
||||
|
||||
@Column
|
||||
private boolean requiresVerification;
|
||||
@Column(nullable = false)
|
||||
private TaskMode taskMode = TaskMode.RANDOM;
|
||||
@Column
|
||||
private PenaltyType penaltyType;
|
||||
@Column
|
||||
private Integer penaltyValue;
|
||||
|
||||
public TaskMode getTaskCardMode() {
|
||||
return taskMode != null ? taskMode : TaskMode.RANDOM;
|
||||
}
|
||||
|
||||
public TimeLockTemplate toTimeLockTemplate() {
|
||||
return new TimeLockTemplate(templateId, owner, name, minTimeInMinutes, maxTimeInMinutes, endTimeVisible,
|
||||
hygineOpeningDurationMinutes, hygineOpeningEveryMinites, tasks, taskEveryMinutes, minTasksPerDay,
|
||||
spinningWheelEntries, spinsEveryMinutes, minSpinsPerDay, requiresVerification, taskMode, penaltyType,
|
||||
return new TimeLockTemplate(getTemplateId(), getOwner(), getName(), minTimeInMinutes, maxTimeInMinutes, endTimeVisible,
|
||||
getHygineOpeningDurationMinutes(), getHygineOpeningEveryMinites(), getTasks(), taskEveryMinutes, minTasksPerDay,
|
||||
spinningWheelEntries, spinsEveryMinutes, minSpinsPerDay, isRequiresVerification(), getTaskMode(), penaltyType,
|
||||
penaltyValue);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,6 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface TimelockTemplateRepository extends JpaRepository<TimeLockTemplateEntity, UUID> {
|
||||
public interface TimeLockTemplateRepository extends JpaRepository<TimeLockTemplateEntity, UUID> {
|
||||
List<TimeLockTemplateEntity> findByOwner(UUID owner);
|
||||
}
|
||||
@@ -13,9 +13,9 @@ import de.oaa.xxx.util.ValidationResult;
|
||||
|
||||
public class TimeLockTemplateService {
|
||||
|
||||
private TimelockTemplateRepository timelockTemplateRepository;
|
||||
private TimeLockTemplateRepository timelockTemplateRepository;
|
||||
|
||||
public TimeLockTemplateService(TimelockTemplateRepository timelockTemplateRepository) {
|
||||
public TimeLockTemplateService(TimeLockTemplateRepository timelockTemplateRepository) {
|
||||
this.timelockTemplateRepository = timelockTemplateRepository;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
package de.oaa.xxx.games.chastity.verification;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import de.oaa.xxx.games.chastity.common.CodeCreator;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/verification")
|
||||
@Transactional
|
||||
public class VerificationController {
|
||||
|
||||
|
||||
|
||||
private final VerificationRepository verificationRepository;
|
||||
private final VerificationVoteRepository verificationVoteRepository;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public VerificationController(VerificationRepository verificationRepository,
|
||||
VerificationVoteRepository verificationVoteRepository, UserRepository userRepository) {
|
||||
this.verificationRepository = verificationRepository;
|
||||
this.verificationVoteRepository = verificationVoteRepository;
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@GetMapping("/{verificationId}")
|
||||
public ResponseEntity<VerificationDTO> get(@PathVariable UUID verificationId) {
|
||||
var optional = verificationRepository.findById(verificationId);
|
||||
if (optional.isEmpty()) {
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
var dto = optional.get().toVerification();
|
||||
verificationVoteRepository.findAllByVerificationId(verificationId).stream()
|
||||
.map(VerificationVoteEntity::toVerificationVote)
|
||||
.forEach(dto.votes()::add);
|
||||
return ResponseEntity.ok(dto);
|
||||
}
|
||||
|
||||
@GetMapping("/")
|
||||
public ResponseEntity<List<VerificationDTO>> getAll(
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "10") int size) {
|
||||
var paging = PageRequest.of(page, size, Sort.by("verificationTime").descending());
|
||||
Page<VerificationEntity> result = verificationRepository.findAllByImageIsNotNull(paging);
|
||||
return ResponseEntity.ok(result.stream().map(VerificationEntity::toVerification).toList());
|
||||
}
|
||||
|
||||
@GetMapping("/new")
|
||||
public ResponseEntity<VerificationDTO> createVerification() {
|
||||
var verification = new VerificationEntity();
|
||||
verification.setVerficationId(UUID.randomUUID());
|
||||
verification.setCode(CodeCreator.createAlphanumericCode(6));
|
||||
verification.setVerificationTime(LocalDateTime.now());
|
||||
verificationRepository.save(verification);
|
||||
return ResponseEntity.ok(verification.toVerification());
|
||||
}
|
||||
|
||||
@PutMapping("/{verificationId}")
|
||||
public ResponseEntity<Void> update(@PathVariable UUID verificationId, @RequestBody VerificationDTO dto,
|
||||
Principal principal) {
|
||||
var user = userRepository.findByEmail(principal.getName()).orElse(null);
|
||||
if (user == null) {
|
||||
return ResponseEntity.status(401).build();
|
||||
}
|
||||
var entity = verificationRepository.findById(verificationId).orElse(null);
|
||||
if (entity == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
if (entity.getVerificationTime().isBefore(LocalDateTime.now().minusHours(1))) {
|
||||
return ResponseEntity.status(HttpStatus.GONE).build();
|
||||
}
|
||||
if (dto.image() != null) {
|
||||
entity.setImage(dto.image());
|
||||
}
|
||||
verificationRepository.save(entity);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@GetMapping("/community")
|
||||
public ResponseEntity<List<Map<String, Object>>> getCommunity(
|
||||
@RequestParam(defaultValue = "0") int page,
|
||||
Principal principal) {
|
||||
var user = userRepository.findByEmail(principal.getName()).orElse(null);
|
||||
if (user == null) return ResponseEntity.status(401).build();
|
||||
UUID myId = user.getUserId();
|
||||
|
||||
LocalDateTime since = LocalDateTime.now().minusHours(24);
|
||||
LocalDateTime until = LocalDateTime.now();
|
||||
var paging = PageRequest.of(page, 10, Sort.by("verificationTime").descending());
|
||||
Page<VerificationEntity> result = verificationRepository
|
||||
.findByKeyholderIsNullAndVerificationTimeBetweenAndImageIsNotNull(since, until, paging);
|
||||
|
||||
List<Map<String, Object>> items = result.getContent().stream().map(v -> {
|
||||
var votes = verificationVoteRepository.findAllByVerificationId(v.getVerficationId());
|
||||
long upvotes = votes.stream().filter(VerificationVoteEntity::isUpvote).count();
|
||||
long downvotes = votes.stream().filter(vt -> !vt.isUpvote()).count();
|
||||
var myVoteOpt = votes.stream().filter(vt -> myId.equals(vt.getUserId())).findFirst();
|
||||
boolean isOwn = myId.equals(v.getLockeeId());
|
||||
|
||||
Map<String, Object> item = new HashMap<>();
|
||||
item.put("verificationId", v.getVerficationId().toString());
|
||||
item.put("verificationTime", v.getVerificationTime().toString());
|
||||
item.put("code", v.getCode());
|
||||
item.put("image", v.getImage() != null ? Base64.getEncoder().encodeToString(v.getImage()) : null);
|
||||
item.put("upvotes", upvotes);
|
||||
item.put("downvotes", downvotes);
|
||||
item.put("myVote", isOwn ? "own" : myVoteOpt.map(VerificationVoteEntity::isUpvote).orElse(null));
|
||||
item.put("hasMore", result.hasNext());
|
||||
return item;
|
||||
}).toList();
|
||||
|
||||
return ResponseEntity.ok(items);
|
||||
}
|
||||
|
||||
@PostMapping("/{verificationId}/vote/")
|
||||
public ResponseEntity<Void> addVote(@PathVariable UUID verificationId, @RequestBody VerificationVoteDTO dto,
|
||||
Principal principal) {
|
||||
var user = userRepository.findByEmail(principal.getName()).orElse(null);
|
||||
if (user == null) return ResponseEntity.status(401).build();
|
||||
if (!verificationRepository.existsById(verificationId)) return ResponseEntity.notFound().build();
|
||||
|
||||
var vEntity = verificationRepository.findById(verificationId).orElse(null);
|
||||
if (vEntity == null) return ResponseEntity.notFound().build();
|
||||
if (user.getUserId().equals(vEntity.getLockeeId())) return ResponseEntity.status(403).build();
|
||||
if (verificationVoteRepository.findByVerificationIdAndUserId(verificationId, user.getUserId()).isPresent()) {
|
||||
return ResponseEntity.status(409).build();
|
||||
}
|
||||
var vote = new VerificationVoteEntity();
|
||||
vote.setVoteId(UUID.randomUUID());
|
||||
vote.setVerificationId(verificationId);
|
||||
vote.setUserId(user.getUserId());
|
||||
vote.setUpvote(dto.upvote());
|
||||
verificationVoteRepository.save(vote);
|
||||
return ResponseEntity.accepted().build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package de.oaa.xxx.games.chastity.verification;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public record VerificationDTO(UUID verficationId, String code, LocalDateTime verificationTime, byte[] image, List<VerificationVoteDTO> votes) {}
|
||||
@@ -1,47 +0,0 @@
|
||||
package de.oaa.xxx.games.chastity.verification;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "verification")
|
||||
public class VerificationEntity {
|
||||
|
||||
@Id
|
||||
@Column
|
||||
private UUID verficationId;
|
||||
@Column(nullable = false)
|
||||
private UUID lockId;
|
||||
@Column(nullable = false)
|
||||
private String code;
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime verificationTime;
|
||||
@Column(columnDefinition = "MEDIUMBLOB")
|
||||
private byte[] image;
|
||||
@Column
|
||||
private UUID lockeeId;
|
||||
@Column
|
||||
private UUID keyholder;
|
||||
|
||||
public UUID getKeyholderId() {
|
||||
return keyholder;
|
||||
}
|
||||
|
||||
public void setKeyholderId(UUID keyholder) {
|
||||
this.keyholder = keyholder;
|
||||
}
|
||||
|
||||
public VerificationDTO toVerification() {
|
||||
return new VerificationDTO(verficationId, code, verificationTime, image, new ArrayList<>());
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package de.oaa.xxx.games.chastity.verification;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface VerificationRepository extends JpaRepository<VerificationEntity, UUID> {
|
||||
|
||||
org.springframework.data.domain.Page<VerificationEntity> findAllByImageIsNotNull(Pageable pageable);
|
||||
|
||||
java.util.List<VerificationEntity> findByLockId(UUID lockId);
|
||||
|
||||
java.util.List<VerificationEntity> findByLockIdAndVerificationTimeBetweenAndImageIsNotNull(UUID lockId, java.time.LocalDateTime from, java.time.LocalDateTime to);
|
||||
|
||||
java.util.List<VerificationEntity> findByLockIdAndVerificationTimeBetweenAndImageIsNull(UUID lockId, java.time.LocalDateTime from, java.time.LocalDateTime to);
|
||||
|
||||
org.springframework.data.domain.Page<VerificationEntity> findByKeyholderIsNullAndVerificationTimeBetweenAndImageIsNotNull(
|
||||
java.time.LocalDateTime from, java.time.LocalDateTime to, org.springframework.data.domain.Pageable pageable);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package de.oaa.xxx.games.chastity.verification;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record VerificationVoteDTO (UUID voteId, UUID userId, boolean upvote) {}
|
||||
@@ -1,17 +0,0 @@
|
||||
package de.oaa.xxx.games.chastity.verification;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface VerificationVoteRepository extends JpaRepository<VerificationVoteEntity, UUID> {
|
||||
|
||||
List<VerificationVoteEntity> findAllByVerificationId(UUID verificationId);
|
||||
|
||||
java.util.Optional<VerificationVoteEntity> findByVerificationIdAndUserId(UUID verificationId, UUID userId);
|
||||
|
||||
void deleteAllByVerificationId(UUID verificationId);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package de.oaa.xxx.games.chastity.vote;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@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;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package de.oaa.xxx.games.chastity.vote;
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package de.oaa.xxx.games.chastity.vote;
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -777,6 +777,16 @@
|
||||
|
||||
async function loadLock() {
|
||||
const res = await fetch('/keyholder/cardlock/' + lockId);
|
||||
if (res.status === 404) {
|
||||
// Prüfen, ob es ein TimeLock ist
|
||||
const tlRes = await fetch('/keyholder/timelock/' + lockId);
|
||||
if (tlRes.ok) {
|
||||
window.location.replace('/activetimelock.html?lockId=' + lockId);
|
||||
return;
|
||||
}
|
||||
document.getElementById('lockContent').textContent = 'Lock nicht gefunden.';
|
||||
return;
|
||||
}
|
||||
if (!res.ok) {
|
||||
document.getElementById('lockContent').textContent = 'Lock nicht gefunden.';
|
||||
return;
|
||||
|
||||
1091
xxxthegame/src/main/resources/static/activetimelock.html
Normal file
1091
xxxthegame/src/main/resources/static/activetimelock.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,6 @@
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
/* ── Unified feed ── */
|
||||
#feed {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -150,6 +149,30 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ── Pranger-Karte ── */
|
||||
.pillory-card {
|
||||
background: var(--color-card);
|
||||
border: 1px solid rgba(231,76,60,0.35);
|
||||
border-radius: 10px;
|
||||
padding: 0.85rem 1rem;
|
||||
}
|
||||
.pillory-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.4rem;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
.pillory-lockee { font-weight: 600; font-size: 0.92rem; }
|
||||
.pillory-date { font-size: 0.78rem; color: var(--color-muted); }
|
||||
.pillory-reason {
|
||||
font-size: 0.82rem;
|
||||
color: #e74c3c;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.pillory-message { font-size: 0.88rem; }
|
||||
|
||||
.empty-hint {
|
||||
color: var(--color-muted);
|
||||
font-size: 0.9rem;
|
||||
@@ -170,7 +193,7 @@
|
||||
<div class="content">
|
||||
|
||||
<div class="page-title">Community Votes</div>
|
||||
<div class="page-subtitle">Verifikationen & Aufgaben-Abstimmungen</div>
|
||||
<div class="page-subtitle">Verifikationen, Aufgaben-Abstimmungen & Pranger</div>
|
||||
|
||||
<div id="feed"></div>
|
||||
<div class="load-spinner" id="loadSpinner" style="display:none;">Lädt…</div>
|
||||
@@ -186,36 +209,84 @@
|
||||
function esc(s) {
|
||||
return String(s || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
function fmtDateTime(isoStr) {
|
||||
return new Date(isoStr).toLocaleString('de-DE', {day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'});
|
||||
}
|
||||
|
||||
// ── Task vote card builder ─────────────────────────────────────────────────
|
||||
// ── Verifikations-Karte ────────────────────────────────────────────────────
|
||||
|
||||
function buildTaskVoteCard(vote) {
|
||||
const isOwn = vote.isOwnLock;
|
||||
const alreadyVoted = vote.myVote !== null && vote.myVote !== undefined;
|
||||
function buildVerCard(base, detail) {
|
||||
const voted = detail.isOwnLock || detail.myVote !== null && detail.myVote !== undefined;
|
||||
const votedUp = !detail.isOwnLock && detail.myVote === true;
|
||||
const votedDn = !detail.isOwnLock && detail.myVote === false;
|
||||
const id = base.displayId;
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.className = 'vote-card';
|
||||
card.innerHTML = `
|
||||
<div class="vote-card-media">
|
||||
<img class="vote-card-img" src="data:image/jpeg;base64,${detail.image}" alt="Verifikationsbild">
|
||||
<div class="vote-card-code">${esc(detail.code)}</div>
|
||||
</div>
|
||||
<div class="vote-card-body">
|
||||
<div class="vote-meta">Verifikation · ${esc(base.lockeeName)} · ${fmtDateTime(base.createdAt)}</div>
|
||||
<div class="vote-actions">
|
||||
<button class="vote-btn ${votedUp ? 'voted-up' : ''}" id="up-${id}"
|
||||
${voted ? 'disabled' : ''}
|
||||
onclick="castVerVote('${id}', true)">
|
||||
👍 <span class="vote-count" id="upcount-${id}">${detail.upvotes}</span>
|
||||
</button>
|
||||
<button class="vote-btn ${votedDn ? 'voted-down' : ''}" id="dn-${id}"
|
||||
${voted ? 'disabled' : ''}
|
||||
onclick="castVerVote('${id}', false)">
|
||||
👎 <span class="vote-count" id="dncount-${id}">${detail.downvotes}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
return card;
|
||||
}
|
||||
|
||||
async function castVerVote(displayId, upvote) {
|
||||
document.getElementById('up-' + displayId).disabled = true;
|
||||
document.getElementById('dn-' + displayId).disabled = true;
|
||||
|
||||
const res = await fetch(`/games/chastity/community/verification/${displayId}/vote/`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ upvote })
|
||||
});
|
||||
if (res.ok || res.status === 202) {
|
||||
const countEl = document.getElementById(upvote ? 'upcount-' + displayId : 'dncount-' + displayId);
|
||||
countEl.textContent = parseInt(countEl.textContent) + 1;
|
||||
document.getElementById((upvote ? 'up-' : 'dn-') + displayId)
|
||||
.classList.add(upvote ? 'voted-up' : 'voted-down');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Aufgaben-Abstimmungs-Karte ─────────────────────────────────────────────
|
||||
|
||||
function buildTaskVoteCard(base, detail) {
|
||||
const isOwn = detail.isOwnLock;
|
||||
const alreadyVoted = detail.entries.some(e => e.ownVote);
|
||||
const id = base.displayId;
|
||||
|
||||
let optionsHtml = '';
|
||||
(vote.tasks || []).forEach((t, i) => {
|
||||
const count = (vote.voteCounts || [])[i] || 0;
|
||||
const isMyVote = vote.myVote === i;
|
||||
const desc = t.description
|
||||
? `<div style="font-size:0.75rem;color:var(--color-muted);margin-top:0.1rem;">${esc(t.description)}</div>`
|
||||
(detail.entries || []).forEach((e, i) => {
|
||||
const desc = e.description
|
||||
? `<div style="font-size:0.75rem;color:var(--color-muted);margin-top:0.1rem;">${esc(e.description)}</div>`
|
||||
: '';
|
||||
const mins = t.minutes > 0
|
||||
? ` <span style="font-size:0.75rem;color:var(--color-muted);">⏱ ${t.minutes} Min.</span>`
|
||||
const mins = e.minutes > 0
|
||||
? ` <span style="font-size:0.75rem;color:var(--color-muted);">⏱ ${e.minutes} Min.</span>`
|
||||
: '';
|
||||
optionsHtml += `<button class="task-vote-btn ${isMyVote ? 'my-vote' : ''}"
|
||||
id="tvbtn-${vote.voteSessionId}-${i}"
|
||||
optionsHtml += `<button class="task-vote-btn ${e.ownVote ? 'my-vote' : ''}"
|
||||
id="tvbtn-${id}-${i}"
|
||||
${(alreadyVoted || isOwn) ? 'disabled' : ''}
|
||||
onclick="castTaskVote('${vote.voteSessionId}', ${i})">
|
||||
onclick="castTaskVote('${id}', ${i})">
|
||||
<div style="flex:1;min-width:0;">
|
||||
<div style="font-weight:600;">${esc(t.title)}${mins}</div>
|
||||
<div style="font-weight:600;">${esc(e.title)}${mins}</div>
|
||||
${desc}
|
||||
</div>
|
||||
<span class="task-vote-count" id="tvcount-${vote.voteSessionId}-${i}">${count} Stimme${count !== 1 ? 'n' : ''}</span>
|
||||
<span class="task-vote-count" id="tvcount-${id}-${i}">${e.votes} Stimme${e.votes !== 1 ? 'n' : ''}</span>
|
||||
</button>`;
|
||||
});
|
||||
|
||||
@@ -225,181 +296,107 @@
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.className = 'task-vote-card';
|
||||
card.dataset.ts = vote.createdAt;
|
||||
card.innerHTML = `
|
||||
<div class="task-vote-header">
|
||||
<span class="task-vote-lockee">🃏 ${esc(vote.lockeeName)}</span>
|
||||
<span class="task-vote-expires">Endet: ${fmtDateTime(vote.expiresAt)}</span>
|
||||
<span class="task-vote-lockee">🃏 ${esc(base.lockeeName)}</span>
|
||||
<span class="task-vote-expires">Endet: ${fmtDateTime(detail.expiresAt)}</span>
|
||||
</div>
|
||||
<div class="task-vote-options">${optionsHtml}</div>
|
||||
${ownHint}`;
|
||||
return card;
|
||||
}
|
||||
|
||||
async function castTaskVote(voteSessionId, taskIndex) {
|
||||
document.querySelectorAll(`[id^="tvbtn-${voteSessionId}-"]`).forEach(btn => btn.disabled = true);
|
||||
async function castTaskVote(displayId, taskIndex) {
|
||||
document.querySelectorAll(`[id^="tvbtn-${displayId}-"]`).forEach(btn => btn.disabled = true);
|
||||
|
||||
const res = await fetch(`/task-card/community/votes/${voteSessionId}/vote/${taskIndex}`, { method: 'POST' });
|
||||
const res = await fetch(`/games/chastity/community/taskvote/${displayId}/vote/${taskIndex}`, { method: 'POST' });
|
||||
if (res.ok || res.status === 204) {
|
||||
const countEl = document.getElementById(`tvcount-${voteSessionId}-${taskIndex}`);
|
||||
const countEl = document.getElementById(`tvcount-${displayId}-${taskIndex}`);
|
||||
if (countEl) {
|
||||
const next = (parseInt(countEl.textContent) || 0) + 1;
|
||||
countEl.textContent = `${next} Stimme${next !== 1 ? 'n' : ''}`;
|
||||
}
|
||||
const btn = document.getElementById(`tvbtn-${voteSessionId}-${taskIndex}`);
|
||||
if (btn) btn.classList.add('my-vote');
|
||||
document.getElementById(`tvbtn-${displayId}-${taskIndex}`)?.classList.add('my-vote');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Verification card builder ──────────────────────────────────────────────
|
||||
// ── Pranger-Karte ──────────────────────────────────────────────────────────
|
||||
|
||||
function buildVerCard(item) {
|
||||
const isOwn = item.myVote === 'own';
|
||||
const voted = isOwn || (item.myVote !== null && item.myVote !== undefined);
|
||||
const votedUp = !isOwn && item.myVote === true;
|
||||
const votedDn = !isOwn && item.myVote === false;
|
||||
const PILLORY_LABELS = {
|
||||
HYGIENE_OPENING_EXEEDED: 'Hygiene-Öffnung überschritten',
|
||||
KEYHOLDER_DESCESSION: 'Keyholder hat aufgegeben'
|
||||
};
|
||||
|
||||
function buildPilloryCard(base, detail) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'vote-card';
|
||||
card.dataset.ts = item.verificationTime;
|
||||
card.className = 'pillory-card';
|
||||
card.innerHTML = `
|
||||
<div class="vote-card-media">
|
||||
<img class="vote-card-img" src="data:image/jpeg;base64,${item.image}" alt="Verifikationsbild">
|
||||
<div class="vote-card-code">${esc(item.code)}</div>
|
||||
<div class="pillory-header">
|
||||
<span class="pillory-lockee">🔒 ${esc(base.lockeeName)}</span>
|
||||
<span class="pillory-date">${fmtDateTime(base.createdAt)}</span>
|
||||
</div>
|
||||
<div class="vote-card-body">
|
||||
<div class="vote-meta">Verifikation · ${fmtDateTime(item.verificationTime)}</div>
|
||||
<div class="vote-actions">
|
||||
<button class="vote-btn ${votedUp ? 'voted-up' : ''}" id="up-${item.verificationId}"
|
||||
${voted ? 'disabled' : ''}
|
||||
onclick="castVerVote('${item.verificationId}', true)">
|
||||
👍 <span class="vote-count" id="upcount-${item.verificationId}">${item.upvotes}</span>
|
||||
</button>
|
||||
<button class="vote-btn ${votedDn ? 'voted-down' : ''}" id="dn-${item.verificationId}"
|
||||
${voted ? 'disabled' : ''}
|
||||
onclick="castVerVote('${item.verificationId}', false)">
|
||||
👎 <span class="vote-count" id="dncount-${item.verificationId}">${item.downvotes}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
<div class="pillory-reason">⚠️ ${esc(PILLORY_LABELS[detail.reason] || detail.reason)}</div>
|
||||
${detail.message ? `<div class="pillory-message">${esc(detail.message)}</div>` : ''}`;
|
||||
return card;
|
||||
}
|
||||
|
||||
async function castVerVote(verificationId, upvote) {
|
||||
const upBtn = document.getElementById('up-' + verificationId);
|
||||
const dnBtn = document.getElementById('dn-' + verificationId);
|
||||
upBtn.disabled = true;
|
||||
dnBtn.disabled = true;
|
||||
// ── Unified feed mit Paging ────────────────────────────────────────────────
|
||||
|
||||
const res = await fetch('/verification/' + verificationId + '/vote/', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ upvote })
|
||||
});
|
||||
if (res.ok || res.status === 202) {
|
||||
const countEl = document.getElementById(upvote ? 'upcount-' + verificationId : 'dncount-' + verificationId);
|
||||
countEl.textContent = parseInt(countEl.textContent) + 1;
|
||||
(upvote ? upBtn : dnBtn).classList.add(upvote ? 'voted-up' : 'voted-down');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Unified feed ──────────────────────────────────────────────────────────
|
||||
// Strategy: task votes are loaded once (small set); verifications are paginated.
|
||||
// When each verification page loads, we interleave the pending task votes that
|
||||
// belong chronologically before the oldest verification on this page.
|
||||
|
||||
let verPage = 0;
|
||||
let verExhausted = false;
|
||||
let page = 0;
|
||||
let exhausted = false;
|
||||
let loading = false;
|
||||
// Task votes sorted newest-first; items are removed as they get placed in feed
|
||||
let pendingTaskVotes = [];
|
||||
let taskVotesLoaded = false;
|
||||
let totalRendered = 0;
|
||||
let rendered = 0;
|
||||
|
||||
function getTs(isoStr) { return new Date(isoStr).getTime(); }
|
||||
|
||||
// Append cards to feed in the correct order.
|
||||
// `items` must already be sorted newest-first.
|
||||
function appendItems(items) {
|
||||
const feed = document.getElementById('feed');
|
||||
items.forEach(card => feed.appendChild(card));
|
||||
totalRendered += items.length;
|
||||
async function fetchDetail(base) {
|
||||
const urls = {
|
||||
VERIFICATION: `/games/chastity/community/verification/${base.displayId}`,
|
||||
TASK_VOTE: `/games/chastity/community/taskvote/${base.displayId}`,
|
||||
PILLORY: `/games/chastity/community/pillory/${base.displayId}`
|
||||
};
|
||||
const url = urls[base.type];
|
||||
if (!url) return null;
|
||||
try {
|
||||
const r = await fetch(url);
|
||||
return r.ok ? await r.json() : null;
|
||||
} catch(e) { return null; }
|
||||
}
|
||||
|
||||
// Merge verItems (sorted newest-first) with the front of pendingTaskVotes
|
||||
// (also sorted newest-first). Only consume task votes that are >= cutoffMs
|
||||
// so that older task votes wait for later verification pages.
|
||||
// Pass cutoffMs = -Infinity to flush all remaining task votes.
|
||||
function mergeWithTaskVotes(verItems, cutoffMs) {
|
||||
const result = [];
|
||||
let vIdx = 0;
|
||||
while (vIdx < verItems.length || (pendingTaskVotes.length > 0 && getTs(pendingTaskVotes[0].createdAt) >= cutoffMs)) {
|
||||
const verTs = vIdx < verItems.length ? getTs(verItems[vIdx].verificationTime) : -Infinity;
|
||||
const tvTs = pendingTaskVotes.length > 0 && getTs(pendingTaskVotes[0].createdAt) >= cutoffMs
|
||||
? getTs(pendingTaskVotes[0].createdAt)
|
||||
: -Infinity;
|
||||
|
||||
if (tvTs >= verTs) {
|
||||
result.push(buildTaskVoteCard(pendingTaskVotes.shift()));
|
||||
} else if (vIdx < verItems.length) {
|
||||
result.push(buildVerCard(verItems[vIdx++]));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Remaining verifications (task votes exhausted or below cutoff)
|
||||
while (vIdx < verItems.length) {
|
||||
result.push(buildVerCard(verItems[vIdx++]));
|
||||
}
|
||||
return result;
|
||||
function buildCard(base, detail) {
|
||||
if (!detail) return null;
|
||||
if (base.type === 'VERIFICATION') return buildVerCard(base, detail);
|
||||
if (base.type === 'TASK_VOTE') return buildTaskVoteCard(base, detail);
|
||||
if (base.type === 'PILLORY') return buildPilloryCard(base, detail);
|
||||
return null;
|
||||
}
|
||||
|
||||
async function loadMore() {
|
||||
if (loading || verExhausted) return;
|
||||
if (loading || exhausted) return;
|
||||
loading = true;
|
||||
document.getElementById('loadSpinner').style.display = '';
|
||||
|
||||
// Load task votes on first call
|
||||
if (!taskVotesLoaded) {
|
||||
let pageData;
|
||||
try {
|
||||
const r = await fetch('/task-card/community/votes');
|
||||
if (r.ok) {
|
||||
const votes = await r.json();
|
||||
pendingTaskVotes = votes.sort((a, b) => getTs(b.createdAt) - getTs(a.createdAt));
|
||||
}
|
||||
} catch(e) {}
|
||||
taskVotesLoaded = true;
|
||||
}
|
||||
const r = await fetch(`/games/chastity/community?page=${page}&sort=createdAt,desc`);
|
||||
if (!r.ok) { loading = false; document.getElementById('loadSpinner').style.display = 'none'; return; }
|
||||
pageData = await r.json();
|
||||
} catch(e) { loading = false; document.getElementById('loadSpinner').style.display = 'none'; return; }
|
||||
|
||||
let verItems = [];
|
||||
try {
|
||||
const r = await fetch('/verification/community?page=' + verPage);
|
||||
if (r.ok) verItems = await r.json();
|
||||
} catch(e) {}
|
||||
const items = pageData.content || [];
|
||||
if (pageData.last) exhausted = true;
|
||||
page++;
|
||||
|
||||
const details = await Promise.all(items.map(fetchDetail));
|
||||
|
||||
document.getElementById('loadSpinner').style.display = 'none';
|
||||
loading = false;
|
||||
|
||||
let batch;
|
||||
if (verItems.length === 0) {
|
||||
verExhausted = true;
|
||||
batch = mergeWithTaskVotes([], -Infinity);
|
||||
} else {
|
||||
const oldestVerTs = getTs(verItems[verItems.length - 1].verificationTime);
|
||||
if (verItems.length < 10) {
|
||||
verExhausted = true;
|
||||
// Last page: flush all remaining task votes too
|
||||
batch = mergeWithTaskVotes(verItems, -Infinity);
|
||||
} else {
|
||||
verPage++;
|
||||
// Only include task votes newer than the oldest item in this page
|
||||
batch = mergeWithTaskVotes(verItems, oldestVerTs);
|
||||
}
|
||||
}
|
||||
const feed = document.getElementById('feed');
|
||||
items.forEach((base, i) => {
|
||||
const card = buildCard(base, details[i]);
|
||||
if (card) { feed.appendChild(card); rendered++; }
|
||||
});
|
||||
|
||||
if (batch.length > 0) appendItems(batch);
|
||||
|
||||
if (totalRendered === 0 && verExhausted) {
|
||||
if (rendered === 0 && exhausted) {
|
||||
document.getElementById('emptyHint').style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
BIN
xxxthegame/src/main/resources/static/img/card_cum.png
Normal file
BIN
xxxthegame/src/main/resources/static/img/card_cum.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 299 KiB |
BIN
xxxthegame/src/main/resources/static/img/card_cum_caged.png
Normal file
BIN
xxxthegame/src/main/resources/static/img/card_cum_caged.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 301 KiB |
@@ -62,6 +62,22 @@ const CARD_DEFS = [
|
||||
defMin: 0,
|
||||
defMax: 0,
|
||||
},
|
||||
{
|
||||
id: 'CUM',
|
||||
img: '/img/card_cum.png',
|
||||
name: 'Cum',
|
||||
desc: 'Spezielle Karte.',
|
||||
defMin: 0,
|
||||
defMax: 0,
|
||||
},
|
||||
{
|
||||
id: 'CUM_IN_CAGE',
|
||||
img: '/img/card_cum_caged.png',
|
||||
name: 'Cum in Cage',
|
||||
desc: 'Spezielle Karte.',
|
||||
defMin: 0,
|
||||
defMax: 0,
|
||||
},
|
||||
];
|
||||
|
||||
/** Lookup-Objekt für Konsumenten, die nach ID auf Name/Bild/Beschreibung zugreifen. */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -196,7 +196,8 @@
|
||||
<div class="form-section">
|
||||
<div class="form-section-title">Optionen</div>
|
||||
|
||||
<div class="form-row">
|
||||
<!-- CardLock: Längste Dauer -->
|
||||
<div class="form-row" id="rowMaxDuration">
|
||||
<label>Längste Dauer</label>
|
||||
<div class="time-picker">
|
||||
<div class="tp-seg">
|
||||
@@ -220,6 +221,13 @@
|
||||
<div class="form-hint">Das Lock öffnet spätestens nach dieser Zeit automatisch. 0 : 00 = keine Begrenzung.</div>
|
||||
</div>
|
||||
|
||||
<!-- TimeLock: Dauer-Info aus Vorlage -->
|
||||
<div class="form-row" id="rowTimeLockInfo" style="display:none;">
|
||||
<label>Sperrdauer</label>
|
||||
<div id="timeLockDurationText" style="font-size:0.9rem;color:var(--color-text);padding:0.3rem 0;"></div>
|
||||
<div class="form-hint">Die Dauer wird beim Lock-Start zufällig aus dem Bereich der Vorlage gewählt.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row" id="rowUnlockCodeLines">
|
||||
<label for="unlockCodeLines">Anzahl Ziffern des Entsperrcodes</label>
|
||||
<div class="inline-number">
|
||||
@@ -275,7 +283,8 @@
|
||||
let myUserId = null;
|
||||
let myUserName = null;
|
||||
let allFriends = [];
|
||||
let allTemplates = [];
|
||||
let allTemplates = []; // combined; each entry has _type: 'cardlock'|'timelock'
|
||||
let selectedTemplate = null;
|
||||
let comboActiveIdx = -1;
|
||||
|
||||
// ── Boot ──
|
||||
@@ -284,9 +293,16 @@
|
||||
myUserId = user.userId;
|
||||
myUserName = user.name;
|
||||
|
||||
// Templates laden – Pflicht
|
||||
// Templates laden – Pflicht (beide Typen parallel)
|
||||
try {
|
||||
allTemplates = await fetch('/cardlock/templates').then(r => r.ok ? r.json() : []);
|
||||
const [cardTpls, timeTpls] = await Promise.all([
|
||||
fetch('/cardlock/templates').then(r => r.ok ? r.json() : []),
|
||||
fetch('/timelock/templates').then(r => r.ok ? r.json() : [])
|
||||
]);
|
||||
allTemplates = [
|
||||
...cardTpls.map(t => ({ ...t, _type: 'cardlock' })),
|
||||
...timeTpls.map(t => ({ ...t, _type: 'timelock' }))
|
||||
];
|
||||
} catch { allTemplates = []; }
|
||||
|
||||
if (allTemplates.length === 0) {
|
||||
@@ -333,16 +349,20 @@
|
||||
dropdown.innerHTML = `<div class="combo-empty">Keine Vorlagen gefunden.</div>`;
|
||||
} else {
|
||||
filtered.forEach(t => {
|
||||
const badge = t._type === 'timelock' ? '⏱' : '🃏';
|
||||
const label = (t.name || 'Unbenannte Vorlage');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'combo-option';
|
||||
div.dataset.id = t.templateId;
|
||||
div.textContent = t.name || 'Unbenannte Vorlage';
|
||||
div.innerHTML = `${badge} ${label}`;
|
||||
div.addEventListener('mousedown', e => {
|
||||
e.preventDefault();
|
||||
hidden.value = t.templateId;
|
||||
input.value = t.name || 'Unbenannte Vorlage';
|
||||
input.value = badge + ' ' + label;
|
||||
selectedTemplate = t;
|
||||
dropdown.classList.remove('open');
|
||||
clearFieldError('rowTemplate');
|
||||
onTemplateChanged(t);
|
||||
});
|
||||
dropdown.appendChild(div);
|
||||
});
|
||||
@@ -471,6 +491,30 @@
|
||||
input.addEventListener('blur', () => { setTimeout(() => { dropdown.classList.remove('open'); if (!hidden.value) input.value = ''; }, 150); });
|
||||
}
|
||||
|
||||
// ── Template-Typ: Sektionen umschalten ──
|
||||
function onTemplateChanged(t) {
|
||||
const isTimeLock = t._type === 'timelock';
|
||||
document.getElementById('rowMaxDuration').style.display = isTimeLock ? 'none' : '';
|
||||
document.getElementById('rowTimeLockInfo').style.display = isTimeLock ? '' : 'none';
|
||||
if (isTimeLock) {
|
||||
const minM = t.minTimeInMinutes || 0;
|
||||
const maxM = t.maxTimeInMinutes || 0;
|
||||
document.getElementById('timeLockDurationText').textContent =
|
||||
`${fmtMinutes(minM)} – ${fmtMinutes(maxM)}`;
|
||||
}
|
||||
}
|
||||
function fmtMinutes(m) {
|
||||
if (!m) return '0 Min.';
|
||||
const d = Math.floor(m / 1440);
|
||||
const h = Math.floor((m % 1440) / 60);
|
||||
const min = m % 60;
|
||||
const parts = [];
|
||||
if (d) parts.push(d + ' Tag' + (d !== 1 ? 'e' : ''));
|
||||
if (h) parts.push(h + ' Std');
|
||||
if (min) parts.push(min + ' Min.');
|
||||
return parts.join(' ') || '0 Min.';
|
||||
}
|
||||
|
||||
// ── Zeitpicker ──
|
||||
function tpChange(prefix, delta, seg) {
|
||||
let d = parseInt(document.getElementById(prefix + '_d').value) || 0;
|
||||
@@ -542,20 +586,36 @@
|
||||
}
|
||||
clearFieldError('rowTemplate');
|
||||
|
||||
const t = allTemplates.find(x => x.templateId === templateId);
|
||||
const t = selectedTemplate || allTemplates.find(x => x.templateId === templateId);
|
||||
if (!t) { showError('Vorlage nicht gefunden.'); return; }
|
||||
|
||||
const lockeeVal = document.getElementById('lockeeValue').value;
|
||||
const keyholderVal = document.getElementById('keyholderValue').value;
|
||||
const isFriendLockee = lockeeVal && lockeeVal !== myUserId;
|
||||
const unlockCodeLen = isFriendLockee ? null : (parseInt(document.getElementById('unlockCodeLines').value) || 5);
|
||||
const isTestLock = isFriendLockee ? false : document.getElementById('testLock').checked;
|
||||
|
||||
let endpoint, body;
|
||||
|
||||
if (t._type === 'timelock') {
|
||||
endpoint = '/keyholder/timelock';
|
||||
body = {
|
||||
templateId: t.templateId,
|
||||
lockeeUserId: isFriendLockee ? lockeeVal : null,
|
||||
lockeeDetailsVisible: isFriendLockee ? document.getElementById('lockeeDetailsVisible').checked : false,
|
||||
keyholder: isFriendLockee ? null : (keyholderVal || null),
|
||||
testLock: isTestLock,
|
||||
unlockCodeLength: unlockCodeLen,
|
||||
};
|
||||
} else {
|
||||
// CardLock
|
||||
const initialCards = buildInitialCardsFromTemplate(t);
|
||||
if (initialCards.length === 0) {
|
||||
showError('Die gewählte Vorlage enthält keine Karten. Bitte Vorlage prüfen.');
|
||||
return;
|
||||
}
|
||||
|
||||
const body = {
|
||||
endpoint = '/keyholder/cardlock';
|
||||
body = {
|
||||
name: t.name,
|
||||
lockeeUserId: isFriendLockee ? lockeeVal : null,
|
||||
lockeeDetailsVisible: isFriendLockee ? document.getElementById('lockeeDetailsVisible').checked : false,
|
||||
@@ -569,12 +629,13 @@
|
||||
hygineOpeningDurationMinutes: t.hygineOpeningDurationMinutes || null,
|
||||
tasks: t.tasks || [],
|
||||
taskCardMode: t.taskCardMode || 'RANDOM',
|
||||
unlockCodeLines: isFriendLockee ? null : (parseInt(document.getElementById('unlockCodeLines').value) || 5),
|
||||
unlockCodeLines: unlockCodeLen,
|
||||
requiresVerification: t.requiresVerification,
|
||||
testLock: isFriendLockee ? false : document.getElementById('testLock').checked,
|
||||
testLock: isTestLock,
|
||||
};
|
||||
}
|
||||
|
||||
const res = await fetch('/keyholder/cardlock', {
|
||||
const res = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
@@ -605,7 +666,9 @@
|
||||
function showUnlockCodeModal(code, lockId, keyholderPending) {
|
||||
document.getElementById('unlockCodeDisplay').textContent = code;
|
||||
if (keyholderPending) document.getElementById('unlockKeyholderHint').style.display = '';
|
||||
const url = '/activelock.html?lockId=' + lockId + (keyholderPending ? '&keyholderPending=1' : '');
|
||||
const isTimeLock = selectedTemplate && selectedTemplate._type === 'timelock';
|
||||
const targetPage = isTimeLock ? '/activetimelock.html' : '/activelock.html';
|
||||
const url = targetPage + '?lockId=' + lockId + (keyholderPending ? '&keyholderPending=1' : '');
|
||||
document.getElementById('unlockModalBtn').onclick = () => startCodeScramble(code, url);
|
||||
document.getElementById('unlockModal').classList.add('open');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user