Timelock weiter gebastelt

This commit is contained in:
2026-03-21 17:46:08 +01:00
parent 173302e0ee
commit 53e7bcbbcc
84 changed files with 8688 additions and 2225 deletions

View File

@@ -1,5 +1,5 @@
#Fri Mar 20 15:47:06 CET 2026
#Sat Mar 21 08:14:24 CET 2026
display=\:0
host=Mario-Linux
process-id=112524
process-id=23243
user=mario

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +1,7 @@
[ {
"version" : "9.5.0-20260320031124+0000",
"buildTime" : "20260320031124+0000",
"commitId" : "97faa73152fb6d4ea37edf6b3f7590dcbce8b952",
"current" : false,
"snapshot" : true,
"nightly" : false,
"releaseNightly" : true,
"activeRc" : false,
"rcFor" : "",
"milestoneFor" : "",
"broken" : false,
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260320031124+0000-bin.zip",
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260320031124+0000-bin.zip.sha256",
"checksum" : "d28982b60bd15c7f3e13032152fc384f30465713b9c439bd3e159ad758461393",
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.5.0-20260320031124+0000-wrapper.jar.sha256",
"wrapperChecksum" : "f307680272dffdb8e636f1169adfbf693513005c80aa06e8d381f20390a06e6a"
}, {
"version" : "9.6.0-20260319194115+0000",
"buildTime" : "20260319194115+0000",
"commitId" : "eaac62111b6cbb05984176b52e4be56d5249ebf8",
"version" : "9.6.0-20260321035213+0000",
"buildTime" : "20260321035213+0000",
"commitId" : "cc9b7c946cf24a57b2119d39f1a33cf5493ea930",
"current" : false,
"snapshot" : true,
"nightly" : true,
@@ -27,10 +10,27 @@
"rcFor" : "",
"milestoneFor" : "",
"broken" : false,
"downloadUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260319194115+0000-bin.zip",
"checksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260319194115+0000-bin.zip.sha256",
"checksum" : "a72c6e0f1a5ecc7d81768c65a5bdcd8f0af37ba5b05c83df4c45d08b8ce79fce",
"wrapperChecksumUrl" : "https://services.gradle.org/distributions-snapshots/gradle-9.6.0-20260319194115+0000-wrapper.jar.sha256",
"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",
"current" : false,
"snapshot" : true,
"nightly" : false,
"releaseNightly" : true,
"activeRc" : false,
"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",
"wrapperChecksum" : "f307680272dffdb8e636f1169adfbf693513005c80aa06e8d381f20390a06e6a"
}, {
"version" : "9.4.1",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -4,14 +4,13 @@ INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.ec
176453541.index
677104696.index
341080888.index
4134502745.index
774576701.index
4134502745.index
41199409.index
2217896880.index
134995224.index
4025319337.index
900586112.index
9341915.index
2929476459.index
2065500052.index
3051047092.index
@@ -28,8 +27,8 @@ INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.ec
2032345814.index
3839581777.index
2466743981.index
673436610.index
13999064.index
673436610.index
3972616808.index
1914043487.index
3154281632.index
@@ -45,10 +44,10 @@ INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.ec
4020783879.index
2900482015.index
3059431983.index
13156219.index
833027591.index
4088356365.index
13156219.index
37241354.index
4088356365.index
1295630681.index
2701419231.index
3939420913.index
@@ -56,8 +55,8 @@ INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.ec
1318022262.index
773718761.index
2311226047.index
1865797976.index
3539841425.index
1865797976.index
2455962971.index
2576972120.index
2389383899.index

View File

@@ -12,11 +12,17 @@
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.cardlock.GreenCard"/>
<fullyQualifiedTypeName name="java.util.Random"/>
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.CardLockService"/>
<fullyQualifiedTypeName name="jakarta.persistence.Column"/>
<fullyQualifiedTypeName name="java.time.LocalDateTime"/>
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.history.LockHistoryRepository"/>
<fullyQualifiedTypeName name="lombok.Getter"/>
<fullyQualifiedTypeName name="lombok.Setter"/>
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.cardlock.Test"/>
<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.timelock.TimeLockEntity"/>
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.timelock.TimeLockRepository"/>
<fullyQualifiedTypeName name="de.oaa.xxx.games.chastity.verification.VerificationVoteRepository"/>
</qualifiedTypeNameHistroy>

View File

@@ -28,6 +28,8 @@
<item key="updateTextualMatches" value="false"/>
<item key="updateQualifiedNames" value="false"/>
<item key="patterns" value="*"/>
<item key="updateSimilarElements" value="false"/>
<item key="updateSimilarElementsMatchStrategy" value="1"/>
</section>
<section name="org.eclipse.jdt.internal.ui.dialogs.OpenTypeSelectionDialog2">
<item key="ShowStatusLine" value="true"/>
@@ -81,4 +83,11 @@
<section name="NewPackageWizardPage">
<item key="create_package_info_java" value="false"/>
</section>
<section name="NewEnumCreationWizard.dialogBounds">
<item key="DIALOG_X_ORIGIN" value="20"/>
<item key="DIALOG_Y_ORIGIN" value="20"/>
<item key="DIALOG_WIDTH" value="631"/>
<item key="DIALOG_HEIGHT" value="577"/>
<item key="DIALOG_FONT_NAME" value="1|Ubuntu Sans|11.0|0|GTK|1|"/>
</section>
</section>

View File

@@ -31,3 +31,7 @@
2026-03-20 10:34:17,517 [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-20 12:08:01,037 [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-20 15:47:08,644 [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-20 16:26:08,485 [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-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.

View File

@@ -6,4 +6,5 @@
<workingSet aggregate="true" factoryID="org.eclipse.ui.internal.WorkingSetFactory" id="1772786688998_3" label="Window Working Set" name="Aggregate for window 1772786688997"/>
<workingSet aggregate="true" factoryID="org.eclipse.ui.internal.WorkingSetFactory" id="1772792014092_4" label="Window Working Set" name="Aggregate for window 1772792014092"/>
<workingSet aggregate="true" factoryID="org.eclipse.ui.internal.WorkingSetFactory" id="1773603878078_5" label="Window Working Set" name="Aggregate for window 1773600542055"/>
<workingSet aggregate="true" factoryID="org.eclipse.ui.internal.WorkingSetFactory" id="1774020460693_6" label="Window Working Set" name="Aggregate for window 1774020460693"/>
</workingSetManager>

View File

@@ -1,3 +1,3 @@
#Fri Mar 20 15:47:06 CET 2026
#Sat Mar 21 08:14:24 CET 2026
org.eclipse.core.runtime=2
org.eclipse.platform=4.39.0.v20260226-0420

BIN
bilder/card_cum.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

BIN
bilder/card_cum_caged.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

View File

@@ -8,6 +8,8 @@ Verknüpfung der Spiele, wenn das Finish eines aus dem Bereich Chastity ist, wir
Wenn ein Lock für einen User existiert, wird er ohne entsprechend Penis/Vagina zu dem Spiel erstellt.
TODO: Im Time Lock, wenn im Spinning Wheel tasks drin sind, dürfen keine sonst keine Tasks gefordert sein und umgekehrt
Ich kann Spieler einladen zu spielen, dann kriegt die Person eine E-Mail und muss bestätigen, dass es diese PErson ist, sie wird dann ins spiel übernommen
-- Falls fall mit Chastity auftritt wird die Spielpartnerin als Keyholder eingetragen, diese Person darf entscheiden, was für ein Lock das wird.

View File

@@ -138,8 +138,8 @@ public class BdsmGameService {
newLock.setTestLock(false);
newLock.setTaskCardMode(template.getTaskCardMode());
int codeLines = template.getUnlockCodeLines() != null ? template.getUnlockCodeLines() : 5;
newLock.setUnlockCodeLines(codeLines);
int codeLines = template.getUnlockCodeLength() != null ? template.getUnlockCodeLength() : 5;
newLock.setUnlockCodeLength(codeLines);
StringBuilder codeBuilder = new StringBuilder();
java.util.Random rng = new java.util.Random();
for (int i = 0; i < codeLines; i++) codeBuilder.append(rng.nextInt(10));

View File

@@ -1,45 +0,0 @@
package de.oaa.xxx.games.chastity;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Random;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.oaa.xxx.games.chastity.cardlock.CardEnum;
import de.oaa.xxx.games.chastity.cardlock.CardLockEntity;
import de.oaa.xxx.games.chastity.tasks.Task;
public class KeyholderCardLock {
private static final Logger LOGGER = LoggerFactory.getLogger(KeyholderCardLock.class);
private CardLockEntity lock;
public void addCards(List<CardEnum> cards) {
lock.getAvailableCards().addAll(cards);
}
public void freeze() {
var multiplier = lock.getPickEveryMinute() * new Random().nextDouble(1.0, 4.0);
LocalDateTime frozenTill = LocalDateTime.now().plus((long) multiplier, ChronoUnit.MINUTES);
lock.setFrozenUntill(frozenTill);
LOGGER.debug("Frozen by keyholder until {}", lock.getFrozenUntill());
}
public void freeze(LocalDateTime until) {
lock.setFrozenUntill(until);
LOGGER.debug("Frozen by keyholder until {}", lock.getFrozenUntill());
}
public void task() {
}
public void task(Task task) {
}
public void penalty() {
}
}

View File

@@ -1,6 +0,0 @@
package de.oaa.xxx.games.chastity;
public enum LockType {
CARD, TIMED;
}

View File

@@ -1,9 +0,0 @@
package de.oaa.xxx.games.chastity;
public class ProcessLock {
public void oeffnen() {
// TODO Auto-generated method stub
}
}

View File

@@ -1,5 +0,0 @@
package de.oaa.xxx.games.chastity;
public class ProcessTimedLock extends ProcessLock {
}

View File

@@ -1,5 +0,0 @@
package de.oaa.xxx.games.chastity;
public class Timer {
}

View File

@@ -43,6 +43,21 @@ public enum CardEnum {
public Card get() {
return new DoubleUpCard();
}
},
CUM {
@Override
public Card get() {
// TODO Auto-generated method stub
return null;
}
},
CUM_IN_CAGE {
@Override
public Card get() {
// TODO Auto-generated method stub
return null;
}
};

View File

@@ -36,18 +36,26 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import de.oaa.xxx.games.chastity.CodeCreator;
import de.oaa.xxx.games.chastity.KeyholderInvitationEntity;
import de.oaa.xxx.games.chastity.KeyholderInvitationRepository;
import de.oaa.xxx.games.chastity.LockeeInvitationEntity;
import de.oaa.xxx.games.chastity.LockeeInvitationRepository;
import de.oaa.xxx.games.chastity.common.CodeCreator;
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.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;
@@ -60,7 +68,7 @@ public class CardLockController {
private final KeyholderInvitationRepository invitationRepository;
private final VerificationRepository verificationRepository;
private final VerificationVoteRepository verificationVoteRepository;
private final HygieneViolationRepository hygieneViolationRepository;
private final KeyholderNotificationRepository keyholderNotificationRepository;
private final LockeeInvitationRepository lockeeInvitationRepository;
private final AssignedTaskRepository assignedTaskRepository;
private final KeyholderTaskChoiceRepository keyholderTaskChoiceRepository;
@@ -76,7 +84,7 @@ public class CardLockController {
public CardLockController(CardlockRepository cardlockRepository, UserRepository userRepository,
KeyholderInvitationRepository invitationRepository, VerificationRepository verificationRepository,
VerificationVoteRepository verificationVoteRepository,
HygieneViolationRepository hygieneViolationRepository,
KeyholderNotificationRepository keyholderNotificationRepository,
LockeeInvitationRepository lockeeInvitationRepository, AssignedTaskRepository assignedTaskRepository,
KeyholderTaskChoiceRepository keyholderTaskChoiceRepository,
CommunityTaskVoteRepository communityTaskVoteRepository,
@@ -88,7 +96,7 @@ public class CardLockController {
this.invitationRepository = invitationRepository;
this.verificationRepository = verificationRepository;
this.verificationVoteRepository = verificationVoteRepository;
this.hygieneViolationRepository = hygieneViolationRepository;
this.keyholderNotificationRepository = keyholderNotificationRepository;
this.lockeeInvitationRepository = lockeeInvitationRepository;
this.assignedTaskRepository = assignedTaskRepository;
this.keyholderTaskChoiceRepository = keyholderTaskChoiceRepository;
@@ -197,7 +205,7 @@ public class CardLockController {
lock.setRequiresVerification(req.requiresVerification());
lock.setTestLock(req.testLock());
lock.setTaskCardMode(req.taskCardMode() != null ? req.taskCardMode() : "RANDOM");
lock.setUnlockCodeLines(codeLines);
lock.setUnlockCodeLength(codeLines);
lock.setUnlockCode(unlockCode);
LocalDateTime now = LocalDateTime.now();
@@ -322,11 +330,7 @@ public class CardLockController {
if (!l.getLockee().equals(myId))
return ResponseEntity.status(403).build();
l.setHygineOpeningtime(LocalDateTime.now());
cardlockRepository.save(l);
unlockCodeHistoryService.save(myId, l.getLockId(), l.getName(), l.getUnlockCode(), "HYGIENE_OPEN");
cardLockServiceFactory.create(l).startHygieneOpening();
return ResponseEntity.ok(Map.of("unlockCode", l.getUnlockCode(), "durationMinutes",
l.getHygineOpeningDurationMinutes() != null ? l.getHygineOpeningDurationMinutes() : 30));
}
@@ -346,46 +350,8 @@ public class CardLockController {
if (!l.getLockee().equals(myId))
return ResponseEntity.status(403).build();
LocalDateTime now = LocalDateTime.now();
// Overtime berechnen
if (l.getHygineOpeningtime() != null && l.getHygineOpeningDurationMinutes() != null) {
LocalDateTime dueTime = l.getHygineOpeningtime().plusMinutes(l.getHygineOpeningDurationMinutes());
if (now.isAfter(dueTime)) {
long overtimeMinutes = ChronoUnit.MINUTES.between(dueTime, now);
if (l.getKeyholder() == null) {
// Self-Lock: 4-fache Überschreitungszeit einfrieren
if (l.getFrozenUntill() != null) {
l.setFrozenUntill(l.getFrozenUntill().plusMinutes(overtimeMinutes * 4));
} else {
l.setFrozenUntill(now.plusMinutes(overtimeMinutes * 4));
}
} else {
// Keyholder vorhanden: Verletzung protokollieren
HygieneViolationEntity violation = new HygieneViolationEntity();
violation.setLockId(lockId);
violation.setLockeeId(myId);
violation.setKeyholderUserId(l.getKeyholder());
violation.setViolationTime(now);
violation.setOvertimeMinutes(overtimeMinutes);
hygieneViolationRepository.save(violation);
}
}
}
// Nächsten Öffnungszeitpunkt setzen
l.setLastHygineOpening(LocalDateTime.now());
l.setHygineOpeningtime(null);
// Neuen Entsperrcode generieren
int codeLines = l.getUnlockCodeLines() != null ? l.getUnlockCodeLines() : 5;
String newCode = generateUnlockCode(codeLines);
l.setUnlockCode(newCode);
cardlockRepository.save(l);
unlockCodeHistoryService.save(myId, l.getLockId(), l.getName(), newCode, "HYGIENE_CLOSE");
return ResponseEntity.ok(Map.of("newUnlockCode", newCode));
String code = cardLockServiceFactory.create(l).endHygieneOpening();
return ResponseEntity.ok(Map.of("newUnlockCode", code));
}
@PostMapping("/cardlock/{lockId}/task/complete")
@@ -496,9 +462,9 @@ public class CardLockController {
result.put("hygieneEnabled", hygieneEnabled);
result.put("hygieneOpeningDue", hygieneOpeningDue);
result.put("hygieneSecondsRemaining", hygieneSecondsRemaining);
result.put("hygieneOpeningActive", l.getHygineOpeningtime() != null);
result.put("hygieneOpeningActive", l.getTempOpeningTime() != null && TempOpeningReason.HYGIENE == l.getTempOpeningReason());
result.put("hygieneOpeningStarted",
l.getHygineOpeningtime() != null ? l.getHygineOpeningtime().toString() : null);
l.getTempOpeningTime() != null ? l.getTempOpeningTime().toString() : null);
result.put("hygieneDurationMinutes",
l.getHygineOpeningDurationMinutes() != null ? l.getHygineOpeningDurationMinutes() : 0);
result.put("hasKeyholder", l.getKeyholder() != null);
@@ -973,7 +939,7 @@ public class CardLockController {
}
}
var recentViolations = hygieneViolationRepository.findByLockId(lockId).stream()
var notification = keyholderNotificationRepository.findByLockId(lockId).stream()
.sorted((a, b) -> b.getViolationTime().compareTo(a.getViolationTime())).limit(5)
.map(v -> Map.of("time", v.getViolationTime().toString(), "overtimeMinutes", v.getOvertimeMinutes()))
.toList();
@@ -998,7 +964,7 @@ public class CardLockController {
result.put("hygieneEnabled", hygieneEnabled);
result.put("hygieneOpeningDue", hygieneOpeningDue);
result.put("hygieneSecondsRemaining", hygieneSecondsRemaining);
result.put("hygieneOpeningActive", l.getHygineOpeningtime() != null);
result.put("hygieneOpeningActive", l.getTempOpeningTime() != null && TempOpeningReason.HYGIENE == l.getTempOpeningReason());
result.put("requiresVerification", l.isRequiresVerification());
result.put("verificationDue", verificationDue);
result.put("verificationDoneToday", verificationDoneToday);
@@ -1007,7 +973,7 @@ public class CardLockController {
result.put("verificationImage", verificationImage);
result.put("verificationUpvotes", verificationUpvotes);
result.put("verificationDownvotes", verificationDownvotes);
result.put("hygieneViolations", recentViolations);
result.put("hygieneViolations", notification);
result.put("hasTasks", l.getTasks() != null && !l.getTasks().isEmpty());
if (l.getTasks() != null) {
var taskList = l.getTasks().stream().map(t -> {
@@ -1589,6 +1555,8 @@ public class CardLockController {
case FREEZE -> "Freeze";
case RESET -> "Reset";
case DOUBLE_UP -> "Double Up";
case CUM -> "Kommen";
case CUM_IN_CAGE -> "Kommen im Käfig";
};
}

View File

@@ -6,6 +6,7 @@ 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.unlock.TempOpeningReason;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
@@ -60,7 +61,7 @@ public class CardLockEntity {
@Column
private boolean testLock;
@Column
private Integer unlockCodeLines;
private Integer unlockCodeLength;
// State
@Column
@@ -75,7 +76,11 @@ public class CardLockEntity {
@Column
private LocalDateTime lastHygineOpening;
@Column
private LocalDateTime hygineOpeningtime; // If null, not while hygine opening
private LocalDateTime tempOpeningTime; // If null, not while hygine opening
@Column
private Integer tempOpeningDuration;
@Column
private TempOpeningReason tempOpeningReason;
@Column
private LocalDateTime unlockTime;
@Column

View File

@@ -11,33 +11,41 @@ import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.oaa.xxx.games.chastity.ProcessLock;
import de.oaa.xxx.games.chastity.common.AbstractLockService;
import de.oaa.xxx.games.chastity.common.CodeCreator;
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationEntity;
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationRepository;
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.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.chastity.tasks.Task;
import de.oaa.xxx.games.chastity.verification.VerificationEntity;
import de.oaa.xxx.games.chastity.verification.VerificationRepository;
import de.oaa.xxx.games.chastity.verification.VerificationVoteEntity;
import de.oaa.xxx.games.chastity.verification.VerificationVoteRepository;
import de.oaa.xxx.user.UserRepository;
public class CardLockService extends ProcessLock {
public class CardLockService extends AbstractLockService {
private static final Logger LOGGER = LoggerFactory.getLogger(CardLockService.class);
private final CardLockEntity lock;
private VerificationRepository verificationRepository;
private VerificationVoteRepository verificationVoteRepository;
private CardLockRepository cardLockRepository;
private VerificationRepository verificationRepository;
private GameHistoryRepository gameHistoryRepository;
private UserRepository userRepository;
private UnlockCodeHistoryService unlockCodeHistoryService;
private KeyholderNotificationRepository keyholderNotificationRepository;
public CardLockService(CardLockEntity lock, VerificationRepository verificationRepository, VerificationVoteRepository verificationVoteRepository, CardLockRepository cardLockRepository, GameHistoryRepository gameHistoryRepository, UserRepository userRepository) {
this.lock = lock;
this.verificationRepository = verificationRepository;
this.verificationVoteRepository = verificationVoteRepository;
public CardLockService(CardLockEntity lock, VerificationRepository verificationRepository,
VerificationVoteRepository verificationVoteRepository, CardLockRepository cardLockRepository,
GameHistoryRepository gameHistoryRepository, UserRepository userRepository, KeyholderNotificationRepository keyholderNotificationRepository,UnlockCodeHistoryService unlockCodeHistoryService) {
super(verificationVoteRepository);
this.lock = lock;
this.cardLockRepository = cardLockRepository;
this.verificationRepository = verificationRepository;
this.gameHistoryRepository = gameHistoryRepository;
this.userRepository = userRepository;
this.keyholderNotificationRepository = keyholderNotificationRepository;
this.unlockCodeHistoryService = unlockCodeHistoryService;
}
public CardDTO getNextCard() {
@@ -165,19 +173,6 @@ public class CardLockService extends ProcessLock {
lock.getAvailableCards().add(CardEnum.GREEN);
cardLockRepository.save(lock);
}
private boolean isValid(VerificationEntity entity) {
int count = 0;
for (VerificationVoteEntity vote : verificationVoteRepository.findAllByVerificationId(entity.getVerficationId())) {
if (vote.isUpvote()) {
count++;
} else {
count--;
}
}
return count >= 0;
}
public String freeze() {
var multiplier = lock.getPickEveryMinute() * new Random().nextDouble(1.0, 4.0);
@@ -244,4 +239,94 @@ public class CardLockService extends ProcessLock {
}
return "";
}
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() {
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());
}
public String cum(boolean tempUnlock) {
if (tempUnlock) {
tempOperning(TempOpeningReason.CARD, 0); // Je länger man braucht, desto länger wird gefreezed
}
return lock.getUnlockCode();
}
public String endCumming() {
Long overtime = calcOvertime();
if (overtime != null) {
if (lock.getKeyholder() == null) {
freezeForOvertime(overtime);
} else {
reportKeyholder(overtime);
}
}
lock.setTempOpeningDuration(null);
lock.setTempOpeningTime(null);
lock.setTempOpeningReason(null);
var code = CodeCreator.createAlphanumericCode(lock.getUnlockCodeLength());
lock.setUnlockCode(code);
cardLockRepository.save(lock);
return code;
}
}

View File

@@ -1,6 +1,8 @@
package de.oaa.xxx.games.chastity.cardlock;
import de.oaa.xxx.games.history.GameHistoryRepository;
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationRepository;
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.user.UserRepository;
@@ -20,18 +22,24 @@ public class CardLockServiceFactory {
private final VerificationVoteRepository verificationVoteRepository;
private final CardLockRepository cardLockRepository;
private final GameHistoryRepository gameHistoryRepository;
private final UserRepository userRepository;
private final UserRepository userRepository;
private KeyholderNotificationRepository keyholderNotificationRepository;
private final UnlockCodeHistoryService unlockCodeHistoryService;
public CardLockServiceFactory(VerificationRepository verificationRepository,
VerificationVoteRepository verificationVoteRepository,
CardLockRepository cardLockRepository,
GameHistoryRepository gameHistoryRepository,
UserRepository userRepository) {
UserRepository userRepository,
KeyholderNotificationRepository keyholderNotificationRepository,
UnlockCodeHistoryService unlockCodeHistoryService) {
this.verificationRepository = verificationRepository;
this.verificationVoteRepository = verificationVoteRepository;
this.cardLockRepository = cardLockRepository;
this.gameHistoryRepository = gameHistoryRepository;
this.userRepository = userRepository;
this.keyholderNotificationRepository = keyholderNotificationRepository;
this.unlockCodeHistoryService = unlockCodeHistoryService;
}
/**
@@ -44,7 +52,9 @@ public class CardLockServiceFactory {
verificationVoteRepository,
cardLockRepository,
gameHistoryRepository,
userRepository
userRepository,
keyholderNotificationRepository,
unlockCodeHistoryService
);
}
}

View File

@@ -0,0 +1,10 @@
package de.oaa.xxx.games.chastity.cardlock;
public class CumCard implements Card {
@Override
public CardDTO processCard(CardLockService lock) {
return new CardDTO(CardEnum.CUM, lock.cum(true));
}
}

View File

@@ -0,0 +1,10 @@
package de.oaa.xxx.games.chastity.cardlock;
public class CumInCageCard implements Card {
@Override
public CardDTO processCard(CardLockService lock) {
return new CardDTO(CardEnum.CUM_IN_CAGE, lock.cum(false));
}
}

View File

@@ -1,10 +0,0 @@
package de.oaa.xxx.games.chastity.cardlock;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.UUID;
public interface HygieneViolationRepository extends JpaRepository<HygieneViolationEntity, UUID> {
List<HygieneViolationEntity> findByKeyholderUserIdAndNotifiedKeyholderFalse(UUID keyholderUserId);
List<HygieneViolationEntity> findByLockId(UUID lockId);
}

View File

@@ -1,8 +1,12 @@
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;

View File

@@ -16,6 +16,8 @@ import org.springframework.transaction.annotation.Transactional;
import de.oaa.xxx.games.chastity.tasks.AssignedTaskEntity;
import de.oaa.xxx.games.chastity.tasks.AssignedTaskRepository;
import de.oaa.xxx.games.chastity.tasks.Task;
import de.oaa.xxx.games.chastity.vote.CommunityTaskVoteEntryRepository;
import de.oaa.xxx.games.chastity.vote.CommunityTaskVoteRepository;
import de.oaa.xxx.social.SystemMessageService;
@Component

View File

@@ -0,0 +1,30 @@
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;
}
}

View File

@@ -1,4 +1,4 @@
package de.oaa.xxx.games.chastity;
package de.oaa.xxx.games.chastity.common;
import java.util.Random;

View File

@@ -0,0 +1,6 @@
package de.oaa.xxx.games.chastity.common;
public enum LockType {
CARD, TIMED;
}

View File

@@ -0,0 +1,6 @@
package de.oaa.xxx.games.chastity.common;
public enum PenaltyType {
ADD, FREEZE, PILLORY;
}

View File

@@ -1,4 +1,4 @@
package de.oaa.xxx.games.chastity;
package de.oaa.xxx.games.chastity.keyholder;
import jakarta.persistence.*;
import lombok.Getter;

View File

@@ -1,4 +1,4 @@
package de.oaa.xxx.games.chastity;
package de.oaa.xxx.games.chastity.keyholder;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Transactional;

View File

@@ -1,4 +1,4 @@
package de.oaa.xxx.games.chastity.cardlock;
package de.oaa.xxx.games.chastity.keyholder;
import jakarta.persistence.*;
import lombok.Getter;
@@ -7,11 +7,13 @@ import lombok.Setter;
import java.time.LocalDateTime;
import java.util.UUID;
import de.oaa.xxx.games.chastity.unlock.TempOpeningReason;
@Getter
@Setter
@Entity
@Table(name = "hygiene_violation")
public class HygieneViolationEntity {
@Table(name = "keyholder_notification")
public class KeyholderNotificationEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@@ -34,4 +36,7 @@ public class HygieneViolationEntity {
@Column(nullable = false)
private boolean notifiedKeyholder = false;
@Column(nullable = false)
private TempOpeningReason openingReason;
}

View File

@@ -0,0 +1,10 @@
package de.oaa.xxx.games.chastity.keyholder;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.UUID;
public interface KeyholderNotificationRepository extends JpaRepository<KeyholderNotificationEntity, UUID> {
List<KeyholderNotificationEntity> findByKeyholderUserIdAndNotifiedKeyholderFalse(UUID keyholderUserId);
List<KeyholderNotificationEntity> findByLockId(UUID lockId);
}

View File

@@ -1,4 +1,4 @@
package de.oaa.xxx.games.chastity.cardlock;
package de.oaa.xxx.games.chastity.keyholder;
import jakarta.persistence.*;
import lombok.Getter;

View File

@@ -1,4 +1,4 @@
package de.oaa.xxx.games.chastity.cardlock;
package de.oaa.xxx.games.chastity.keyholder;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;

View File

@@ -0,0 +1,16 @@
package de.oaa.xxx.games.chastity.lockcontroll;
public abstract class LockControl {
protected final LockControlCallback callback;
public LockControl(LockControlCallback callback) {
this.callback = callback;
}
public abstract boolean init();
public abstract boolean unlock();
public abstract boolean lock();
}

View File

@@ -0,0 +1,8 @@
package de.oaa.xxx.games.chastity.lockcontroll;
public interface LockControlCallback {
void setUnlockCode(String code);
int getUnlockcodeLenght();
}

View File

@@ -0,0 +1,6 @@
package de.oaa.xxx.games.chastity.lockcontroll;
public enum LockControllType {
TTLOCK, UNLOCK_CODE, TRUST;
}

View File

@@ -0,0 +1,13 @@
package de.oaa.xxx.games.chastity.lockcontroll;
class NoInteractionCallback implements LockControlCallback {
@Override
public void setUnlockCode(String code) {
}
@Override
public int getUnlockcodeLenght() {
return 0;
}
}

View File

@@ -0,0 +1,32 @@
package de.oaa.xxx.games.chastity.lockcontroll;
public class TTLockControl extends LockControl {
public TTLockControl() {
super(new NoInteractionCallback());
}
private static final String BASE_URL = "https://euapi.ttlock.com/";
@Override
public boolean init() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean unlock() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean lock() {
// TODO Auto-generated method stub
return false;
}
}

View File

@@ -0,0 +1,23 @@
package de.oaa.xxx.games.chastity.lockcontroll;
public class TrustLockControl extends LockControl {
public TrustLockControl() {
super(new NoInteractionCallback());
}
@Override
public boolean init() {
return true;
}
@Override
public boolean unlock() {
return true;
}
@Override
public boolean lock() {
return true;
}
}

View File

@@ -0,0 +1,27 @@
package de.oaa.xxx.games.chastity.lockcontroll;
import de.oaa.xxx.games.chastity.common.CodeCreator;
public class UnlockcodeLockControl extends LockControl {
public UnlockcodeLockControl(LockControlCallback callback) {
super(callback);
}
@Override
public boolean init() {
return true;
}
@Override
public boolean unlock() {
return true;
}
@Override
public boolean lock() {
var code = CodeCreator.createAlphanumericCode(callback.getUnlockcodeLenght());
callback.setUnlockCode(code);
return true;
}
}

View File

@@ -1,4 +1,4 @@
package de.oaa.xxx.games.chastity;
package de.oaa.xxx.games.chastity.lockee;
import java.security.Principal;
import java.security.SecureRandom;
@@ -220,7 +220,7 @@ public class LockeeInvitationController {
LocalDateTime now = LocalDateTime.now();
lock.setStartTime(now);
lock.setUnlockCode(unlockCode);
lock.setUnlockCodeLines(codeLines);
lock.setUnlockCodeLength(codeLines);
lock.setAvailableCards(new ArrayList<>(lock.getInitialCards()));
lock.setOpenPicks(0);
lock.setNextCardIn(now.plusMinutes(lock.getPickEveryMinute()));

View File

@@ -1,4 +1,4 @@
package de.oaa.xxx.games.chastity;
package de.oaa.xxx.games.chastity.lockee;
import jakarta.persistence.*;
import lombok.Getter;

View File

@@ -1,4 +1,4 @@
package de.oaa.xxx.games.chastity;
package de.oaa.xxx.games.chastity.lockee;
import org.springframework.data.jpa.repository.JpaRepository;

View File

@@ -0,0 +1,34 @@
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;
}

View File

@@ -0,0 +1,7 @@
package de.oaa.xxx.games.chastity.pillory;
public enum PilloryReason {
HYGIENE_OPENING_EXEEDED,
KEYHOLDER_DESCESSION;
}

View File

@@ -0,0 +1,9 @@
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> {
}

View File

@@ -0,0 +1,51 @@
package de.oaa.xxx.games.chastity.spinningwheel;
import de.oaa.xxx.games.chastity.timelock.TimeLockService;
public enum EntryType {
ADD_TIME {
@Override
public void apply(TimeLockService service, Integer intVal, String stringVal) {
service.addTime(intVal);
}
},
REMOVE_TIME {
@Override
public void apply(TimeLockService service, Integer intVal, String stringVal) {
service.removeTime(intVal);
}
},
FREEZE_TIME {
@Override
public void apply(TimeLockService service, Integer intVal, String stringVal) {
service.freeze(intVal);
}
},
FREEZE {
@Override
public void apply(TimeLockService service, Integer intVal, String stringVal) {
service.freeze();
}
},
UNFREEZE {
@Override
public void apply(TimeLockService service, Integer intVal, String stringVal) {
service.unfreeze();
}
},
TASK {
@Override
public void apply(TimeLockService service, Integer intVal, String stringVal) {
service.task();
}
},
TEXT {
@Override
public void apply(TimeLockService service, Integer intVal, String stringVal) {
service.text(intVal, stringVal);
}
};
public abstract void apply(TimeLockService service, Integer intVal, String stringVal);
}

View File

@@ -0,0 +1,39 @@
package de.oaa.xxx.games.chastity.spinningwheel;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
@Converter
public class SpinningWheelConverter implements AttributeConverter<List<SpinningWheelEntry>, String> {
private static final ObjectMapper mapper = new ObjectMapper();
@Override
public String convertToDatabaseColumn(List<SpinningWheelEntry> list) {
if (list == null || list.isEmpty())
return null;
try {
return mapper.writeValueAsString(list);
} catch (Exception e) {
return null;
}
}
@Override
public List<SpinningWheelEntry> convertToEntityAttribute(String json) {
if (json == null || json.isBlank())
return new ArrayList<>();
try {
return new ArrayList<>(mapper.readValue(json, new TypeReference<List<SpinningWheelEntry>>() {
}));
} catch (Exception e) {
return new ArrayList<>();
}
}
}

View File

@@ -0,0 +1,16 @@
package de.oaa.xxx.games.chastity.spinningwheel;
import java.util.List;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class SpinningWheelEntity {
private UUID wheelId;
private String name;
private List<SpinningWheelEntity> entries;
}

View File

@@ -0,0 +1,17 @@
package de.oaa.xxx.games.chastity.spinningwheel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class SpinningWheelEntry {
private EntryType type;
private Integer intVal;
private String stringVal;
}

View File

@@ -0,0 +1,20 @@
package de.oaa.xxx.games.chastity.spinningwheel;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class SpinningWheelEntryEntity {
private UUID entryId;
private EntryType type;
private Integer intVal;
private String stringVal;
public SpinningWheelEntry toSpinningWheelEntry() {
return new SpinningWheelEntry(type, intVal, stringVal);
}
}

View File

@@ -45,6 +45,9 @@ public class AssignedTaskEntity {
/** Wie viele rote Karten hinzufügen bei Ablehnung / Ablauf (null = keine). */
@Column
private Integer penaltyRedCards;
@Column
private Integer penaltyAddTime;
/** PENDING | ACCEPTED | DECLINED | EXPIRED */
@Column(nullable = false)

View File

@@ -0,0 +1,5 @@
package de.oaa.xxx.games.chastity.tasks;
public enum TaskMode {
RANDOM, KEYHOLDER, COMMUNITY;
}

View File

@@ -0,0 +1,9 @@
package de.oaa.xxx.games.chastity.timelock;
import java.util.UUID;
import de.oaa.xxx.games.chastity.lockcontroll.LockControllType;
public record TimeLockAdditionalSettings(LockControllType controllType, UUID lockee, UUID keyholder, boolean testlock, Integer unlockCodeLength) {
}

View File

@@ -0,0 +1,117 @@
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.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.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 {
@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 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; }
}

View File

@@ -0,0 +1,9 @@
package de.oaa.xxx.games.chastity.timelock;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TimeLockRepository extends JpaRepository<TimeLockEntity, UUID> {
}

View File

@@ -0,0 +1,381 @@
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.CodeCreator;
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationEntity;
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationRepository;
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.user.UserRepository;
public class TimeLockService extends AbstractLockService 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 LockControl lockControl;
public TimeLockService(TimeLockEntity lock, VerificationRepository verificationRepository,
VerificationVoteRepository verificationVoteRepository,
TimeLockRepository timeLockRepository,
GameHistoryRepository gameHistoryRepository,
UserRepository userRepository,
KeyholderNotificationRepository keyholderNotificationRepository,
UnlockCodeHistoryService unlockCodeHistoryService) {
super(verificationVoteRepository);
this.lock = lock;
this.timeLockRepository = timeLockRepository;
this.verificationRepository = verificationRepository;
this.gameHistoryRepository = gameHistoryRepository;
this.userRepository = userRepository;
this.keyholderNotificationRepository = keyholderNotificationRepository;
this.unlockCodeHistoryService = unlockCodeHistoryService;
}
public void init(TimeLockTemplate 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());
lock.setLockee(settings.lockee());
lock.setKeyholder(settings.keyholder());
lock.setRequiresVerification(template.requiresVerification());
lock.setTestLock(settings.testlock());
lock.setUnlockCodeLength(settings.unlockCodeLength());
lock.setStartTime(LocalDateTime.now());
Integer unlockTimeMinutes = template.maxTimeInMinutes();
if (template.minTimeInMinutes() != null) {
unlockTimeMinutes = new Random().nextInt(template.minTimeInMinutes(), template.maxTimeInMinutes());
}
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());
lockControl.lock();
}
public SpinningWheelEntry spinWheel() {
if (TempOpeningReason.HYGIENE != lock.getTempOpeningReason() ) {
var entries = lock.getSpinningWheelEntries();
var entry = entries.get(new Random().nextInt(entries.size()));
entry.getType().apply(this, entry.getIntVal(), entry.getStringVal());
return entry;
}
// Nicht während der Hyhiene Öffnung
return null;
}
public void addTime(Integer intVal) {
LOGGER.debug("Lock addTime: %s minutes", intVal);
lock.setUnlockTime(lock.getUnlockTime().plusMinutes(intVal));
}
public void removeTime(Integer intVal) {
LOGGER.debug("Lock removeTime: %s minutes", intVal);
lock.setUnlockTime(lock.getUnlockTime().minusMinutes(intVal));
}
public void freeze(Integer intVal) {
LOGGER.debug("Lock frozen for %s minutes", intVal);
lock.setFrozenFrom(LocalDateTime.now());
lock.setFrozenUntil(LocalDateTime.now().plusMinutes(intVal));
}
public void freeze() {
LOGGER.debug("Lock frozen");
lock.setFrozenFrom(LocalDateTime.now());
}
public void unfreeze() {
if (lock.getFrozenFrom() != null) {
var unfreeTime = lock.getFrozenUntil() != null ? lock.getFrozenUntil() : LocalDateTime.now();
var diff = ChronoUnit.MINUTES.between(lock.getFrozenFrom(), unfreeTime);
LOGGER.debug("Lock unfrozen - adding %s minutes to the lock", diff);
lock.setUnlockTime(lock.getUnlockTime().plusMinutes(diff));
} else {
LOGGER.debug("Lock not frozen - ignore Call");
}
}
public void task() {
if (TempOpeningReason.HYGIENE != lock.getTempOpeningReason() ) {
switch (lock.getTaskMode()) {
case TaskMode.RANDOM -> applyRandomTask();
case TaskMode.KEYHOLDER -> startKeyHolderVote();
case TaskMode.COMMUNITY -> startCommunityVode();
}
}
// 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()));
}
}
public void text(Integer intVal, String stringVal) {
LOGGER.debug("Apply text {}", stringVal);
lock.setCurrentTask(stringVal);
if (intVal != null && intVal > 0) {
lock.setTaskUntil(LocalDateTime.now().plusMinutes(intVal));
}
}
public String clearTask() {
LOGGER.debug("Clear task");
lock.setCurrentTask(null);
lock.setCurrentTaskDescription(null);
lock.setTaskUntil(null);
return "";
}
public void testUnfreeze() {
if (lock.getFrozenUntil().isAfter(LocalDateTime.now())) {
unfreeze();
}
}
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);
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 applyPenalty() {
if (lock.getPenaltyType() != null) {
switch (lock.getPenaltyType()) {
case ADD -> addTime(lock.getPenaltyValue());
case FREEZE -> freeze();
case PILLORY -> pillory();
}
}
}
public void check() {
LocalDate today = LocalDate.now();
if (!lock.getStartTime().toLocalDate().equals(today)) {
if (lock.getLastCheck() != null || today.isAfter(lock.getLastCheck())) {
LOGGER.info("Check the day before for violations");
LocalDate yesterday = today.minusDays(1);
boolean violation = false;
if (lock.getMinTasksPerDay() != null) {
if (lock.getMinTasksPerDay() > lock.getTaskTimes().stream().map(LocalDateTime::toLocalDate)
.filter(yesterday::equals).count()) {
violation = true;
}
}
if (lock.getMinSpinsPerDay() != null) {
if (lock.getMinSpinsPerDay() > lock.getSpinningWheelTimes().stream().map(LocalDateTime::toLocalDate)
.filter(yesterday::equals).count()) {
violation = true;
}
}
if (violation) {
applyPenalty();
}
lock.setLastCheck(today);
timeLockRepository.save(lock);
}
}
}
public void pillory() {
// TODO an den Pranger stellen
}
@Override
public void setUnlockCode(String code) {
lock.setUnlockCode(code);
timeLockRepository.save(lock);
}
@Override
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());
}
}

View File

@@ -0,0 +1,54 @@
package de.oaa.xxx.games.chastity.timelock;
import org.springframework.stereotype.Service;
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationRepository;
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.user.UserRepository;
@Service
public class TimeLockServiceFactory {
private final VerificationRepository verificationRepository;
private final VerificationVoteRepository verificationVoteRepository;
private final TimeLockRepository timeLockRepository;
private final GameHistoryRepository gameHistoryRepository;
private final UserRepository userRepository;
private KeyholderNotificationRepository keyholderNotificationRepository;
private final UnlockCodeHistoryService unlockCodeHistoryService;
public TimeLockServiceFactory(VerificationRepository verificationRepository,
VerificationVoteRepository verificationVoteRepository,
TimeLockRepository timeLockRepository,
GameHistoryRepository gameHistoryRepository,
UserRepository userRepository,
KeyholderNotificationRepository keyholderNotificationRepository,
UnlockCodeHistoryService unlockCodeHistoryService) {
this.verificationRepository = verificationRepository;
this.verificationVoteRepository = verificationVoteRepository;
this.timeLockRepository = timeLockRepository;
this.gameHistoryRepository = gameHistoryRepository;
this.userRepository = userRepository;
this.keyholderNotificationRepository = keyholderNotificationRepository;
this.unlockCodeHistoryService = unlockCodeHistoryService;
}
/**
* 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
);
}
}

View File

@@ -0,0 +1,16 @@
package de.oaa.xxx.games.chastity.timelock;
import java.util.List;
import java.util.UUID;
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;
public record TimeLockTemplate(UUID templateId, UUID owner, 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) {
}

View File

@@ -0,0 +1,107 @@
package de.oaa.xxx.games.chastity.timelock;
import java.util.List;
import java.util.UUID;
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.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 {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column
private UUID templateId;
@Column(nullable = false)
private UUID owner;
@Column
private String name;
@Column
private Integer minTimeInMinutes;
@Column
private Integer maxTimeInMinutes;
@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;
@Column
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,
penaltyValue);
}
public static TimeLockTemplateEntity fromTemplate(TimeLockTemplate template) {
if (template != null) {
TimeLockTemplateEntity entity = new TimeLockTemplateEntity();
entity.setOwner(template.owner());
entity.setName(template.name());
entity.setMinTimeInMinutes(template.minTimeInMinutes());
entity.setMaxTimeInMinutes(template.maxTimeInMinutes());
entity.setEndTimeVisible(template.endTimeVisible());
entity.setHygineOpeningDurationMinutes(template.hygineOpeningDurationMinutes());
entity.setHygineOpeningEveryMinites(template.hygineOpeningEveryMinites());
entity.setTasks(template.tasks());
entity.setTaskEveryMinutes(template.taskEveryMinutes());
entity.setMinTasksPerDay(template.minTasksPerDay());
entity.setSpinningWheelEntries(template.spinningWheelEntries());
entity.setSpinsEveryMinutes(template.spinsEveryMinutes());
entity.setMinSpinsPerDay(template.minSpinsPerDay());
entity.setRequiresVerification(template.requiresVerification());
entity.setTaskMode(template.taskMode() != null ? template.taskMode() : TaskMode.RANDOM);
entity.setPenaltyType(template.penaltyType());
entity.setPenaltyValue(template.penaltyValue());
return entity;
}
return null;
}
}

View File

@@ -0,0 +1,92 @@
package de.oaa.xxx.games.chastity.timelock;
import static de.oaa.xxx.util.ValidationResult.ERROR;
import static de.oaa.xxx.util.ValidationResult.OK;
import static de.oaa.xxx.util.ValidationResult.WARNING;
import java.util.List;
import java.util.UUID;
import de.oaa.xxx.games.chastity.spinningwheel.EntryType;
import de.oaa.xxx.games.chastity.tasks.TaskMode;
import de.oaa.xxx.util.ValidationResult;
public class TimeLockTemplateService {
private TimelockTemplateRepository timelockTemplateRepository;
public TimeLockTemplateService(TimelockTemplateRepository timelockTemplateRepository) {
this.timelockTemplateRepository = timelockTemplateRepository;
}
public List<TimeLockTemplate> all(UUID ownderId) {
return timelockTemplateRepository.findByOwner(ownderId).stream().map(template -> template.toTimeLockTemplate()).toList();
}
public boolean safe(TimeLockTemplate template) {
var result = validate(template);
if (ValidationResult.OK == result || ValidationResult.WARNING == result) {
timelockTemplateRepository.save(TimeLockTemplateEntity.fromTemplate(template));
return true;
}
return false;
}
public ValidationResult validate(TimeLockTemplate template) {
if (template == null) {
return ERROR;
}
if (template.owner() == null ||
template.name() == null ||
template.maxTimeInMinutes() == null) {
return ERROR;
}
if (template.taskEveryMinutes() != null) {
if (template.tasks() == null || template.tasks().isEmpty()) {
return ERROR;
}
if (template.spinningWheelEntries() != null && !template.spinningWheelEntries().isEmpty()
&& template.spinningWheelEntries().stream().anyMatch(entry -> EntryType.TASK == entry.getType())) {
return ERROR;
}
if (template.minTasksPerDay() != null) {
int minTime = getMinimumTime(template);
if (minTime > (24*60)) {
return ERROR;
} else if (minTime > (12*60)) {
return WARNING;
}
}
} else if (template.minTasksPerDay() != null) {
return ERROR;
}
if (template.spinsEveryMinutes() != null) {
if (template.spinningWheelEntries() == null || template.spinningWheelEntries().isEmpty()) {
return ERROR;
}
if (template.minSpinsPerDay() != null) {
int minTime = template.spinsEveryMinutes() * template.minSpinsPerDay();
if (minTime > (24*60)) {
return ERROR;
} else if (minTime > (12*60)) {
return WARNING;
}
}
} else if (template.minSpinsPerDay() != null) {
return ERROR;
}
if (template.hygineOpeningEveryMinites() != null && template.hygineOpeningDurationMinutes() == null) {
return ERROR;
}
return OK;
}
private Integer getMinimumTime(TimeLockTemplate template) {
int votetime = 0;
if (TaskMode.COMMUNITY == template.taskMode() || TaskMode.KEYHOLDER == template.taskMode()) {
votetime = 60 * template.minTasksPerDay();
}
int waittime = template.taskEveryMinutes() * template.minTasksPerDay();
return votetime + waittime;
}
}

View File

@@ -0,0 +1,10 @@
package de.oaa.xxx.games.chastity.timelock;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.UUID;
public interface TimelockTemplateRepository extends JpaRepository<TimeLockTemplateEntity, UUID> {
List<TimeLockTemplateEntity> findByOwner(UUID owner);
}

View File

@@ -0,0 +1,5 @@
package de.oaa.xxx.games.chastity.unlock;
public enum TempOpeningReason {
HYGIENE, CARD, TASK;
}

View File

@@ -1,4 +1,4 @@
package de.oaa.xxx.games.chastity.cardlock;
package de.oaa.xxx.games.chastity.unlock;
import jakarta.persistence.*;
import lombok.Getter;

View File

@@ -1,4 +1,4 @@
package de.oaa.xxx.games.chastity.cardlock;
package de.oaa.xxx.games.chastity.unlock;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

View File

@@ -1,4 +1,4 @@
package de.oaa.xxx.games.chastity.cardlock;
package de.oaa.xxx.games.chastity.unlock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -23,7 +23,7 @@ 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.CodeCreator;
import de.oaa.xxx.games.chastity.common.CodeCreator;
import de.oaa.xxx.user.UserRepository;
@RestController

View File

@@ -1,4 +1,4 @@
package de.oaa.xxx.games.chastity.cardlock;
package de.oaa.xxx.games.chastity.vote;
import jakarta.persistence.*;
import lombok.Getter;

View File

@@ -1,4 +1,4 @@
package de.oaa.xxx.games.chastity.cardlock;
package de.oaa.xxx.games.chastity.vote;
import jakarta.persistence.*;
import lombok.Getter;

View File

@@ -1,4 +1,4 @@
package de.oaa.xxx.games.chastity.cardlock;
package de.oaa.xxx.games.chastity.vote;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;

View File

@@ -1,4 +1,4 @@
package de.oaa.xxx.games.chastity.cardlock;
package de.oaa.xxx.games.chastity.vote;
import org.springframework.data.jpa.repository.JpaRepository;
import java.time.LocalDateTime;

View File

@@ -0,0 +1,5 @@
package de.oaa.xxx.util;
public enum ValidationResult {
OK, INFO, WARNING, ERROR;
}

View File

@@ -0,0 +1,150 @@
package de.oaa.xxx.games.chastity.timelock;
import de.oaa.xxx.games.chastity.spinningwheel.EntryType;
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.util.ValidationResult;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
class TimeLockTemplateServiceTest {
private final TimeLockTemplateService service = new TimeLockTemplateService(null);
/**
* Zentrale Factory-Methode für den Test.
* Erzeugt immer ein frisches Record-Objekt.
*/
private TimeLockTemplate createTemplate(
UUID owner, String name, Integer maxTime,
Integer taskEvery, Integer minTasks, List<Task> tasks,
Integer spinsEvery, Integer minSpins, List<SpinningWheelEntry> entries,
Integer hygEvery, Integer hygDuration) {
return createTemplate(owner, name, maxTime, taskEvery, minTasks, tasks, spinsEvery, minSpins, entries, hygEvery, hygDuration, TaskMode.RANDOM);
}
private TimeLockTemplate createTemplate(
UUID owner, String name, Integer maxTime,
Integer taskEvery, Integer minTasks, List<Task> tasks,
Integer spinsEvery, Integer minSpins, List<SpinningWheelEntry> entries,
Integer hygEvery, Integer hygDuration, TaskMode taskMode) {
return new TimeLockTemplate(null,
owner,
name,
0, // minTimeInMinutes
maxTime,
true, // endTimeVisible
hygDuration,
hygEvery,
tasks,
taskEvery,
minTasks,
entries,
spinsEvery,
minSpins,
false, // requiresVerification
taskMode,
null, // penaltyType
null // penaltyValue
);
}
// Hilfsmethode für ein schnelles Standard-Template
private TimeLockTemplate validBase() {
return createTemplate(UUID.randomUUID(), "Standard", 60, null, null, null, null, null, null, null, null);
}
@Test
void validate_OK_Minimal() {
assertThat(service.validate(validBase())).isEqualTo(ValidationResult.OK);
}
@Test
void validate_ERROR_MissingMandatoryFields() {
assertThat(service.validate(createTemplate(null, "Name", 10, null, null, null, null, null, null, null, null))).isEqualTo(ValidationResult.ERROR);
assertThat(service.validate(createTemplate(UUID.randomUUID(), null, 10, null, null, null, null, null, null, null, null))).isEqualTo(ValidationResult.ERROR);
assertThat(service.validate(createTemplate(UUID.randomUUID(), "Name", null, null, null, null, null, null, null, null, null))).isEqualTo(ValidationResult.ERROR);
}
@Test
void validate_ERROR_TasksButNoList() {
// Intervall gesetzt, aber Liste ist null oder leer
TimeLockTemplate tNull = createTemplate(UUID.randomUUID(), "T", 60, 10, null, null, null, null, null, null, null);
TimeLockTemplate tEmpty = createTemplate(UUID.randomUUID(), "T", 60, 10, null, Collections.emptyList(), null, null, null, null, null);
assertThat(service.validate(tNull)).isEqualTo(ValidationResult.ERROR);
assertThat(service.validate(tEmpty)).isEqualTo(ValidationResult.ERROR);
}
@Test
void validate_ERROR_SpinningWheelTaskConflict() {
SpinningWheelEntry taskEntry = new SpinningWheelEntry();
taskEntry.setType(EntryType.TASK);
TimeLockTemplate t = createTemplate(UUID.randomUUID(), "T", 60, 10, null, List.of(new Task()), null, null, List.of(taskEntry), null, null);
assertThat(service.validate(t)).isEqualTo(ValidationResult.ERROR);
}
@Test
void validate_WARNING_TaskTimeLimitReached() {
// 120 Min Intervall * 7 Tasks = 840 Min (> 12h)
TimeLockTemplate t = createTemplate(UUID.randomUUID(), "T", 60, 120, 7, List.of(new Task()), null, null, null, null, null);
assertThat(service.validate(t)).isEqualTo(ValidationResult.WARNING);
}
@Test
void validate_ERROR_MinTasksWithoutInterval() {
TimeLockTemplate t = createTemplate(UUID.randomUUID(), "T", 60, null, 5, null, null, null, null, null, null);
assertThat(service.validate(t)).isEqualTo(ValidationResult.ERROR);
}
@Test
void validate_ERROR_SpinsButNoEntries() {
TimeLockTemplate t = createTemplate(UUID.randomUUID(), "T", 60, null, null, null, 30, 2, null, null, null);
assertThat(service.validate(t)).isEqualTo(ValidationResult.ERROR);
}
@Test
void validate_WARNING_SpinTimeLimitReached() {
// 200 Min Intervall * 4 Spins = 800 Min (> 12h)
TimeLockTemplate t = createTemplate(UUID.randomUUID(), "T", 60, null, null, null, 200, 4, List.of(new SpinningWheelEntry()), null, null);
assertThat(service.validate(t)).isEqualTo(ValidationResult.WARNING);
}
@Test
void validate_ERROR_HygieneIncomplete() {
// Intervall gesetzt, aber Dauer fehlt
TimeLockTemplate t = createTemplate(UUID.randomUUID(), "T", 60, null, null, null, null, null, null, 60, null);
assertThat(service.validate(t)).isEqualTo(ValidationResult.ERROR);
}
@Test
public void validate_communityVotes() {
TimeLockTemplate t = createTemplate(UUID.randomUUID(), "T", 60, 60, 6, List.of(new Task()), null, null, null, null, null,TaskMode.RANDOM);
assertThat(service.validate(t)).isEqualTo(ValidationResult.OK);
t = createTemplate(UUID.randomUUID(), "T", 60, 60, 7, List.of(new Task()), null, null, null, null, null,TaskMode.COMMUNITY);
assertThat(service.validate(t)).isEqualTo(ValidationResult.WARNING);
t = createTemplate(UUID.randomUUID(), "T", 60, 60, 12, List.of(new Task()), null, null, null, null, null,TaskMode.COMMUNITY);
assertThat(service.validate(t)).isEqualTo(ValidationResult.WARNING);
}
public void testErrorOnTasksAndTaskInWheel() {
SpinningWheelEntry entry = Mockito.mock(SpinningWheelEntry.class);
Mockito.when(entry.getType()).thenReturn(EntryType.TASK);
TimeLockTemplate t = createTemplate(UUID.randomUUID(), "T", 60, 60, 6, List.of(new Task()), null, null, List.of(), null, null);
assertThat(service.validate(t)).isEqualTo(ValidationResult.ERROR);
}
}