Weiter am Timelock gearbeitet
This commit is contained in:
@@ -40,7 +40,19 @@
|
||||
"Bash(stat /home/mario/Workspaces/xxx-thegame/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/ttlock/*)",
|
||||
"Bash(git -C /home/mario/Workspaces/xxx-thegame diff HEAD xxxthegame/src/main/resources/static/neulock.html)",
|
||||
"Bash(1:*)",
|
||||
"Bash(python3:*)"
|
||||
"Bash(python3:*)",
|
||||
"Bash(head -8 echo \"---\" grep -n \"✎\\\\|edit\\\\|bearbeit\" /home/mario/Workspaces/xxx-thegame/xxxthegame/src/main/resources/static/aufgaben.html)",
|
||||
"Bash(head -8 echo \"---\" grep -n \"🚿\\\\|hygiene\\\\|hygien\" /home/mario/Workspaces/xxx-thegame/xxxthegame/src/main/resources/static/activelock.html)",
|
||||
"Bash(head -8 echo \"---\" grep -n \"🆘\\\\|SOS\\\\|nothilfe\\\\|emer\" /home/mario/Workspaces/xxx-thegame/xxxthegame/src/main/resources/static/activelock.html)",
|
||||
"Bash(head -8 echo \"---\" grep -n \"👍\\\\|👎\\\\|vote\\\\|abstimm\" /home/mario/Workspaces/xxx-thegame/xxxthegame/src/main/resources/static/communityvotes.html)",
|
||||
"Bash(head -8 echo \"---\" grep -n \"🧊\\\\|ice\\\\|eis\\\\|freeze\" /home/mario/Workspaces/xxx-thegame/xxxthegame/src/main/resources/static/activetimelock.html)",
|
||||
"Bash(head -8 echo \"---\" grep -n \"📁\\\\|folder\\\\|archiv\\\\|histor\" /home/mario/Workspaces/xxx-thegame/xxxthegame/src/main/resources/static/activelock.html)",
|
||||
"Bash(head -8 echo \"---\" grep -n \"🔄\\\\|refresh\\\\|reload\\\\|neu laden\" /home/mario/Workspaces/xxx-thegame/xxxthegame/src/main/resources/static/activelock.html)",
|
||||
"Bash(head -8 echo \"---\" grep -n \"🔥\\\\|fire\\\\|hot\" /home/mario/Workspaces/xxx-thegame/xxxthegame/src/main/resources/static/nachrichten.html)",
|
||||
"Bash(head -5 echo \"---\" grep -n \"⬆\\\\|⬇\\\\|sort\\\\|up\\\\|down\" /home/mario/Workspaces/xxx-thegame/xxxthegame/src/main/resources/static/admin.html)",
|
||||
"Bash(head -8 echo \"---\" grep -n \"⭐\\\\|star\\\\|premium\\\\|abo\" /home/mario/Workspaces/xxx-thegame/xxxthegame/src/main/resources/static/abonnements.html)",
|
||||
"Bash(head -8 echo \"---\" grep -n \"👁\\\\|view\\\\|sichtbar\\\\|Ansicht\" /home/mario/Workspaces/xxx-thegame/xxxthegame/src/main/resources/static/einladungen.html)",
|
||||
"Bash(/dev/null echo:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#Wed Mar 25 07:26:10 CET 2026
|
||||
#Wed Mar 25 21:32:43 CET 2026
|
||||
display=\:0
|
||||
host=mario-mint
|
||||
process-id=4033
|
||||
process-id=43084
|
||||
user=mario
|
||||
|
||||
@@ -754,3 +754,68 @@ Binding(CTRL+SHIFT+T,
|
||||
|
||||
!ENTRY org.springframework.tooling.boot.ls 1 0 2026-03-25 16:28:18.002
|
||||
!MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS
|
||||
!SESSION 2026-03-25 21:32:37.936 -----------------------------------------------
|
||||
eclipse.buildId=4.39.0.20260305-0817
|
||||
java.version=21.0.6
|
||||
java.vendor=Eclipse Adoptium
|
||||
BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=de_DE
|
||||
Framework arguments: -product org.eclipse.epp.package.java.product
|
||||
Command-line arguments: -os linux -ws gtk -arch x86_64 -clean -product org.eclipse.epp.package.java.product
|
||||
|
||||
!ENTRY ch.qos.logback.classic 1 0 2026-03-25 21:32:39.444
|
||||
!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized.
|
||||
|
||||
!ENTRY ch.qos.logback.classic 1 0 2026-03-25 21:32:44.209
|
||||
!MESSAGE Logback config file: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.m2e.logback/logback.2.7.101.20251017-1242.xml
|
||||
|
||||
!ENTRY org.eclipse.ui 2 0 2026-03-25 21:32:44.327
|
||||
!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
|
||||
!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-25 21:32:44.327
|
||||
!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
|
||||
|
||||
!ENTRY org.eclipse.ui 2 0 2026-03-25 21:32:44.476
|
||||
!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
|
||||
!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-25 21:32:44.476
|
||||
!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
|
||||
|
||||
!ENTRY org.eclipse.jface 2 0 2026-03-25 21:33:18.884
|
||||
!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation.
|
||||
!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-25 21:33:18.884
|
||||
!MESSAGE A conflict occurred for CTRL+SHIFT+T:
|
||||
Binding(CTRL+SHIFT+T,
|
||||
ParameterizedCommand(Command(org.eclipse.jdt.ui.navigate.open.type,Open Type,
|
||||
Open a type in a Java editor,
|
||||
Category(org.eclipse.ui.category.navigate,Navigate,null,true),
|
||||
WorkbenchHandlerServiceHandler("org.eclipse.jdt.ui.navigate.open.type"),
|
||||
,,true),null),
|
||||
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||
org.eclipse.ui.contexts.window,,,system)
|
||||
Binding(CTRL+SHIFT+T,
|
||||
ParameterizedCommand(Command(org.eclipse.lsp4e.symbolInWorkspace,Go to Symbol in Workspace,
|
||||
,
|
||||
Category(org.eclipse.lsp4e.category,Language Servers,null,true),
|
||||
WorkbenchHandlerServiceHandler("org.eclipse.lsp4e.symbolInWorkspace"),
|
||||
,,true),null),
|
||||
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||
org.eclipse.ui.contexts.window,,,system)
|
||||
|
||||
!ENTRY org.eclipse.jface 2 0 2026-03-25 22:47:11.586
|
||||
!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation.
|
||||
!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-25 22:47:11.586
|
||||
!MESSAGE A conflict occurred for CTRL+R:
|
||||
Binding(CTRL+R,
|
||||
ParameterizedCommand(Command(org.eclipse.debug.ui.commands.RunToLine,Run to Line,
|
||||
Resume and break when execution reaches the current line,
|
||||
Category(org.eclipse.debug.ui.category.run,Run/Debug,Run/Debug command category,true),
|
||||
WorkbenchHandlerServiceHandler("org.eclipse.debug.ui.commands.RunToLine"),
|
||||
,,true),null),
|
||||
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||
org.eclipse.debug.ui.debugging,,,system)
|
||||
Binding(CTRL+R,
|
||||
ParameterizedCommand(Command(org.springframework.ide.eclipse.boot.restart.commands.restart,Trigger Restart,
|
||||
Restart Spring Boot Application,
|
||||
Category(org.eclipse.debug.ui.category.run,Run/Debug,Run/Debug command category,true),
|
||||
WorkbenchHandlerServiceHandler("org.springframework.ide.eclipse.boot.restart.commands.restart"),
|
||||
,,true),null),
|
||||
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||
org.eclipse.debug.ui.console,,,system)
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -8,3 +8,4 @@
|
||||
2026-03-24 06:41:47,661 [Worker-2: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
||||
2026-03-24 11:26:24,107 [Worker-2: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
||||
2026-03-25 07:26:14,133 [Worker-5: 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-25 21:32:47,427 [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 @@
|
||||
#Wed Mar 25 07:26:10 CET 2026
|
||||
#Wed Mar 25 21:32:43 CET 2026
|
||||
org.eclipse.core.runtime=2
|
||||
org.eclipse.platform=4.39.0.v20260226-0420
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
Anzeige der Locks und Lockees im Profil, Anzeige der Locked time, falls verifiziert, die verification muss einmal am tag statt finden
|
||||
|
||||
Historie für die Spiele
|
||||
|
||||
Sammeln von Erfahrung
|
||||
|
||||
Verknüpfung der Spiele, wenn das Finish eines aus dem Bereich Chastity ist, wird daraus sofort ein Lock erstellt
|
||||
|
||||
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
|
||||
|
||||
@@ -28,7 +28,7 @@ import lombok.Setter;
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "current_lock")
|
||||
@Table(name = "active_lock")
|
||||
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
|
||||
@DiscriminatorColumn(name = "lock_type", discriminatorType = DiscriminatorType.STRING)
|
||||
public class BaseLockEntity {
|
||||
|
||||
@@ -42,6 +42,7 @@ 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.games.chastity.unlock.UnlockCodeHistoryService;
|
||||
import de.oaa.xxx.social.SystemMessageService;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
|
||||
@@ -59,6 +60,7 @@ public class TimeLockController {
|
||||
private final CommunityVerificationRepository verificationRepository;
|
||||
private final CommunityVerificationVoteRepository verificationVoteRepository;
|
||||
private final SubscriptionLimitService subscriptionLimitService;
|
||||
private final UnlockCodeHistoryService unlockCodeHistoryService;
|
||||
|
||||
public TimeLockController(TimeLockRepository timeLockRepository,
|
||||
TimeLockTemplateRepository templateRepository,
|
||||
@@ -69,7 +71,8 @@ public class TimeLockController {
|
||||
TimeLockServiceFactory timeLockServiceFactory,
|
||||
CommunityVerificationRepository verificationRepository,
|
||||
CommunityVerificationVoteRepository verificationVoteRepository,
|
||||
SubscriptionLimitService subscriptionLimitService) {
|
||||
SubscriptionLimitService subscriptionLimitService,
|
||||
UnlockCodeHistoryService unlockCodeHistoryService) {
|
||||
this.timeLockRepository = timeLockRepository;
|
||||
this.templateRepository = templateRepository;
|
||||
this.userRepository = userRepository;
|
||||
@@ -80,6 +83,7 @@ public class TimeLockController {
|
||||
this.verificationRepository = verificationRepository;
|
||||
this.verificationVoteRepository = verificationVoteRepository;
|
||||
this.subscriptionLimitService = subscriptionLimitService;
|
||||
this.unlockCodeHistoryService = unlockCodeHistoryService;
|
||||
}
|
||||
|
||||
// ── Erstellen ────────────────────────────────────────────────────────────────
|
||||
@@ -357,7 +361,7 @@ public class TimeLockController {
|
||||
}
|
||||
|
||||
result.put("keyholderRequestedUnlock", l.isKeyholderRequestedUnlock());
|
||||
if (l.isKeyholderRequestedUnlock() || l.isTestLock()) {
|
||||
if (l.isKeyholderRequestedUnlock() || l.isTestLock() || timeUp) {
|
||||
result.put("unlockCode", l.getUnlockCode() != null ? l.getUnlockCode() : "");
|
||||
}
|
||||
result.put("emergencyUnlockRequested", l.getEmergencyUnlockRequestedAt() != null);
|
||||
@@ -584,6 +588,10 @@ public class TimeLockController {
|
||||
}
|
||||
|
||||
TimeLockService service = timeLockServiceFactory.create(l);
|
||||
if (l.getUnlockCode() != null && !l.getUnlockCode().isBlank()) {
|
||||
String reason = l.isKeyholderRequestedUnlock() ? "KEYHOLDER_UNLOCK" : "LOCK_OPEN";
|
||||
unlockCodeHistoryService.save(myId, lockId, l.getName(), l.getUnlockCode(), reason);
|
||||
}
|
||||
service.unlock(l.getUnlockCode());
|
||||
|
||||
// Clean up verifications
|
||||
@@ -596,6 +604,123 @@ public class TimeLockController {
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
// ── Keyholder-Ansicht ─────────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/timelock/as-keyholder")
|
||||
public ResponseEntity<List<Map<String, Object>>> getTimeLocksAsKeyholder(Principal principal) {
|
||||
var meOpt = userRepository.findByEmail(principal.getName());
|
||||
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||
UUID myId = meOpt.get().getUserId();
|
||||
|
||||
var locks = timeLockRepository.findByKeyholderAndStartTimeIsNotNullAndUnlockTimeIsNull(myId);
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (var lock : locks) {
|
||||
var lockeeOpt = userRepository.findById(lock.getLockee());
|
||||
if (lockeeOpt.isEmpty()) continue;
|
||||
var lockee = lockeeOpt.get();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
boolean isFrozen = lock.getFrozenFrom() != null
|
||||
&& (lock.getFrozenUntil() == null || lock.getFrozenUntil().isAfter(now));
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("lockId", lock.getLockId().toString());
|
||||
item.put("lockType", "TIMELOCK");
|
||||
item.put("lockName", lock.getName() != null ? lock.getName() : "TimeLock");
|
||||
item.put("lockeeName", lockee.getName());
|
||||
item.put("lockeeId", lockee.getUserId().toString());
|
||||
item.put("lockeeProfilePic", lockee.getProfilePicture());
|
||||
item.put("startTime", lock.getStartTime() != null ? lock.getStartTime().toString() : null);
|
||||
item.put("isFrozen", isFrozen);
|
||||
item.put("emergencyUnlockRequested", lock.getEmergencyUnlockRequestedAt() != null);
|
||||
result.add(item);
|
||||
}
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
@GetMapping("/timelock/as-keyholder/{lockId}")
|
||||
public ResponseEntity<Map<String, Object>> getTimeLockAsKeyholder(
|
||||
@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 (!myId.equals(l.getKeyholder())) return ResponseEntity.status(403).build();
|
||||
|
||||
var lockeeOpt = userRepository.findById(l.getLockee());
|
||||
if (lockeeOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
var lockee = lockeeOpt.get();
|
||||
|
||||
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));
|
||||
|
||||
// Verifikation
|
||||
boolean verificationDue = false;
|
||||
boolean verificationDoneToday = false;
|
||||
String verificationMyVote = null;
|
||||
String verificationTodayId = null;
|
||||
if (l.isRequiresVerification()) {
|
||||
LocalDateTime todayStart = LocalDate.now().atStartOfDay();
|
||||
LocalDateTime todayEnd = todayStart.plusDays(1);
|
||||
var completed = verificationRepository
|
||||
.findByLockIdAndCreatedAtBetweenAndImageIsNotNull(lockId, todayStart, todayEnd);
|
||||
if (!completed.isEmpty()) {
|
||||
verificationDoneToday = true;
|
||||
var todayV = completed.get(0);
|
||||
verificationTodayId = todayV.getDisplayId().toString();
|
||||
var myVote = verificationVoteRepository
|
||||
.findAllByVerificationId(todayV.getDisplayId()).stream()
|
||||
.filter(v -> myId.equals(v.getVoteId())).findFirst();
|
||||
verificationMyVote = myVote.map(v -> v.isUpvote() ? "upvote" : "downvote").orElse(null);
|
||||
} else {
|
||||
verificationDue = true;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("lockId", l.getLockId().toString());
|
||||
result.put("lockType", "TIMELOCK");
|
||||
result.put("lockName", l.getName() != null ? l.getName() : "TimeLock");
|
||||
result.put("lockeeId", lockee.getUserId().toString());
|
||||
result.put("lockeeName", lockee.getName());
|
||||
result.put("lockeeProfilePic", lockee.getProfilePicture());
|
||||
result.put("startTime", l.getStartTime() != null ? l.getStartTime().toString() : null);
|
||||
result.put("unlockTime", (l.isEndTimeVisible() || timeUp)
|
||||
? (l.getUnlockTime() != null ? l.getUnlockTime().toString() : null) : null);
|
||||
result.put("timeUp", timeUp);
|
||||
result.put("isFrozen", isFrozen);
|
||||
result.put("frozenUntil", l.getFrozenUntil() != null ? l.getFrozenUntil().toString() : null);
|
||||
result.put("emergencyUnlockRequested", l.getEmergencyUnlockRequestedAt() != null);
|
||||
result.put("keyholderRequestedUnlock", l.isKeyholderRequestedUnlock());
|
||||
result.put("requiresVerification", l.isRequiresVerification());
|
||||
result.put("verificationDue", verificationDue);
|
||||
result.put("verificationDoneToday", verificationDoneToday);
|
||||
result.put("verificationMyVote", verificationMyVote);
|
||||
result.put("verificationTodayId", verificationTodayId);
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
@PostMapping("/timelock/as-keyholder/{lockId}/request-unlock")
|
||||
@Transactional
|
||||
public ResponseEntity<?> requestUnlockAsKeyholder(
|
||||
@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 (!myId.equals(l.getKeyholder())) return ResponseEntity.status(403).build();
|
||||
|
||||
l.setKeyholderRequestedUnlock(true);
|
||||
timeLockRepository.save(l);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
// ── Notfall-Entsperrung ───────────────────────────────────────────────────────
|
||||
|
||||
@PostMapping("/timelock/{lockId}/emergency-unlock")
|
||||
@@ -615,15 +740,21 @@ public class TimeLockController {
|
||||
|
||||
l.setEmergencyUnlockRequestedAt(LocalDateTime.now());
|
||||
if (l.getKeyholder() == null) {
|
||||
// Kein Keyholder: sofort entsperren, Lock als ungültig markieren (keine Historie, keine XP)
|
||||
l.setEmergencyAutoUnlocked(true);
|
||||
l.setKeyholderRequestedUnlock(true);
|
||||
timeLockRepository.save(l);
|
||||
if (l.getUnlockCode() != null && !l.getUnlockCode().isBlank()) {
|
||||
unlockCodeHistoryService.save(myId, lockId, l.getName(), l.getUnlockCode(), "EMERGENCY_UNLOCK");
|
||||
}
|
||||
return ResponseEntity.ok(Map.of("unlockCode", l.getUnlockCode() != null ? l.getUnlockCode() : ""));
|
||||
} 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();
|
||||
}
|
||||
timeLockRepository.save(l);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
// ── Hilfsmethoden ─────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.oaa.xxx.games.chastity.timelock;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
@@ -10,7 +11,9 @@ public interface TimeLockRepository extends JpaRepository<TimeLockEntity, UUID>
|
||||
|
||||
boolean existsByLockeeAndStartTimeIsNotNullAndUnlockTimeIsNull(UUID lockee);
|
||||
|
||||
@Modifying
|
||||
List<TimeLockEntity> findByKeyholderAndStartTimeIsNotNullAndUnlockTimeIsNull(UUID keyholder);
|
||||
|
||||
@Modifying(clearAutomatically = true)
|
||||
@Query("DELETE FROM TimeLockEntity t WHERE t.lockId = :lockId")
|
||||
void deleteByLockId(UUID lockId);
|
||||
}
|
||||
|
||||
@@ -1208,26 +1208,9 @@
|
||||
async function lockLoeschen() {
|
||||
closeWarnModal();
|
||||
try {
|
||||
const res = await fetch('/keyholder/timelock/' + lockId, { method: 'DELETE' });
|
||||
if (res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (data.unlockCode) {
|
||||
// Code kurz anzeigen bevor redirect
|
||||
const area = document.getElementById('lockActionArea');
|
||||
area.innerHTML = `
|
||||
<div style="text-align:center;padding:1.5rem;background:rgba(46,204,113,0.08);border:1px solid rgba(46,204,113,0.3);border-radius:10px;">
|
||||
<div style="font-size:0.85rem;color:var(--color-muted);margin-bottom:0.5rem;">Dein Entsperrcode:</div>
|
||||
<div style="font-size:2rem;font-weight:700;font-family:monospace;letter-spacing:0.2em;color:#2ecc71;">${data.unlockCode}</div>
|
||||
<div style="font-size:0.82rem;color:var(--color-muted);margin-top:0.5rem;">Lock erfolgreich beendet.</div>
|
||||
</div>`;
|
||||
setTimeout(() => { window.location.href = '/userhome.html'; }, 5000);
|
||||
} else {
|
||||
window.location.href = '/userhome.html';
|
||||
}
|
||||
}
|
||||
} catch(_) {
|
||||
window.location.href = '/userhome.html';
|
||||
}
|
||||
await fetch('/keyholder/timelock/' + lockId, { method: 'DELETE' });
|
||||
} catch(_) { /* ignorieren */ }
|
||||
window.location.href = '/userhome.html';
|
||||
}
|
||||
|
||||
function openEmergencyModal() {
|
||||
@@ -1253,13 +1236,30 @@
|
||||
document.getElementById('emergencyModalActions').style.display = 'none';
|
||||
try {
|
||||
const res = await fetch('/keyholder/timelock/' + lockId + '/emergency-unlock', { method: 'POST' });
|
||||
if (res.ok || res.status === 204) {
|
||||
const hasKH2 = _currentLock && _currentLock.keyholderName;
|
||||
const successText = hasKH2
|
||||
? `✅ Notfall-Anfrage gesendet. ${_currentLock.keyholderName} wurde benachrichtigt.`
|
||||
: `✅ Notfall-Freigabe ausgelöst. Das Lock öffnet sich jetzt.`;
|
||||
if (res.status === 200) {
|
||||
// Kein Keyholder: Entsperrcode direkt anzeigen, Lock als ungültig markiert
|
||||
const data = await res.json().catch(() => ({}));
|
||||
document.getElementById('emergencyModalContent').innerHTML = `
|
||||
<p style="font-size:0.85rem;color:var(--color-muted);line-height:1.5;margin:0 0 0.75rem;">
|
||||
⚠️ Das Lock wird als <strong style="color:var(--color-text);">ungültig</strong> gewertet –
|
||||
kein Historieneintrag, keine XP.
|
||||
</p>
|
||||
<div style="font-size:2rem;font-weight:700;font-family:monospace;letter-spacing:0.2em;
|
||||
text-align:center;background:var(--color-secondary);border-radius:8px;
|
||||
padding:0.75rem 1rem;color:var(--color-primary);margin-bottom:0.5rem;">
|
||||
${data.unlockCode || '–'}
|
||||
</div>`;
|
||||
const btn = document.getElementById('btnEmergencyConfirm');
|
||||
btn.textContent = 'OK – Lock beenden';
|
||||
btn.onclick = async () => { closeEmergencyModal(); await lockLoeschen(); };
|
||||
document.getElementById('emergencyModalActions').style.display = '';
|
||||
} else if (res.status === 204) {
|
||||
// Keyholder vorhanden: Benachrichtigung gesendet
|
||||
const khName = _currentLock && _currentLock.keyholderName;
|
||||
document.getElementById('emergencyModalContent').innerHTML =
|
||||
`<p style="font-size:0.88rem;color:#2ecc71;line-height:1.5;margin:0;">${successText}</p>`;
|
||||
`<p style="font-size:0.88rem;color:#2ecc71;line-height:1.5;margin:0;">
|
||||
✅ Notfall-Anfrage gesendet. ${khName} wurde benachrichtigt.
|
||||
</p>`;
|
||||
setTimeout(() => { closeEmergencyModal(); loadLock(); }, 2000);
|
||||
}
|
||||
} catch(e) {
|
||||
|
||||
@@ -24,6 +24,9 @@ window.ICONS = {
|
||||
CHECK: { type: 'emoji', value: '✅' },
|
||||
DISCOVER: { type: 'emoji', value: '🗺️' },
|
||||
ARROW: { type: 'emoji', value: '▶️' },
|
||||
REFRESH: { type: 'emoji', value: '🔄' }, // Erneuern / Neu laden
|
||||
START: { type: 'emoji', value: '🚀' }, // Starten / Los
|
||||
CELEBRATE: { type: 'emoji', value: '🎉' }, // Erfolg / Abschluss
|
||||
|
||||
// ── UI-Symbole ────────────────────────────────────────────────────────
|
||||
CLOSE: { type: 'symbol', value: '✕' }, // Schließen / Ablehnen / Löschen
|
||||
@@ -35,13 +38,47 @@ window.ICONS = {
|
||||
LIGHTNING: { type: 'emoji', value: '⚡' }, // Aktion (z. B. Zeit entfernen)
|
||||
EMOJI_PICKER: { type: 'emoji', value: '😊' }, // Emoji-Picker öffnen
|
||||
REMOVE: { type: 'symbol', value: '⊗' }, // Eintrag/Spiel entfernen
|
||||
EDIT: { type: 'symbol', value: '✎' }, // Bearbeiten-Button
|
||||
TRASH: { type: 'emoji', value: '🗑' }, // Löschen-Button
|
||||
WARNING: { type: 'emoji', value: '⚠️' }, // Warnung / Hinweis
|
||||
REPORT: { type: 'symbol', value: '⚑' }, // Melden-Button (Flag)
|
||||
VISIBILITY: { type: 'emoji', value: '👁' }, // Sichtbar / Details sichtbar
|
||||
THUMBS_UP: { type: 'emoji', value: '👍' }, // Upvote / Zustimmung
|
||||
THUMBS_DOWN: { type: 'emoji', value: '👎' }, // Downvote / Ablehnung
|
||||
ARROW_UP: { type: 'symbol', value: '⬆' }, // Sortierung aufsteigend
|
||||
ARROW_DOWN: { type: 'symbol', value: '⬇' }, // Sortierung absteigend
|
||||
NAV_PREV: { type: 'symbol', value: '←' }, // Zurück / Vorheriges Bild
|
||||
NAV_NEXT: { type: 'symbol', value: '→' }, // Weiter / Nächstes Bild
|
||||
CAROUSEL_PREV: { type: 'symbol', value: '‹' }, // Karussell zurück
|
||||
CAROUSEL_NEXT: { type: 'symbol', value: '›' }, // Karussell weiter
|
||||
TIP: { type: 'emoji', value: '💡' }, // Hinweis / Tipp
|
||||
DOT_RED: { type: 'emoji', value: '🔴' }, // Status-Indikator rot
|
||||
COMING_SOON: { type: 'emoji', value: '🚧' }, // In Entwicklung / Demnächst
|
||||
|
||||
// ── Chastity Game ─────────────────────────────────────────────────────
|
||||
NEW_LOCK: { type: 'emoji', value: '🆕' },
|
||||
LOCK: { type: 'emoji', value: '🔒' },
|
||||
UNLOCK: { type: 'emoji', value: '🔓' }, // Entsperren
|
||||
LOCKED_SECURE: { type: 'emoji', value: '🔐' }, // Sicher gesperrt (mit Schlüssel)
|
||||
KEY: { type: 'emoji', value: '🔑' },
|
||||
HISTORY: { type: 'emoji', value: '🔙' },
|
||||
VOTES: { type: 'emoji', value: '🗳️' },
|
||||
TRUST: { type: 'emoji', value: '🤝' }, // Trust-Lock
|
||||
EMERGENCY: { type: 'emoji', value: '🆘' }, // Notfall-Entsperrung
|
||||
HYGIENE: { type: 'emoji', value: '🚿' }, // Hygiene-Öffnung
|
||||
FROZEN: { type: 'emoji', value: '❄️' }, // Eingefroren (zeitlich)
|
||||
FROZEN_HARD: { type: 'emoji', value: '🧊' }, // Eingefroren (unlimitiert)
|
||||
UNFREEZE: { type: 'emoji', value: '🌊' }, // Aufgetaut / Unfreeze
|
||||
CODE_DIGITS: { type: 'emoji', value: '🔢' }, // Zahlenkombination / PIN-Länge
|
||||
|
||||
// ── CardLock ──────────────────────────────────────────────────────────
|
||||
CARD: { type: 'emoji', value: '🃏' }, // Karte (standalone)
|
||||
DICE: { type: 'emoji', value: '🎲' }, // Zufällig / Würfeln
|
||||
|
||||
// ── TimeLock / Spinning Wheel ──────────────────────────────────────────
|
||||
SPINNING_WHEEL: { type: 'emoji', value: '🎡' }, // Glücksrad drehen
|
||||
TASK_ACTIVE: { type: 'emoji', value: '🎯' }, // Aktuelle Aufgabe
|
||||
CLOCK: { type: 'emoji', value: '🕐' }, // Uhr / Zeitpunkt
|
||||
|
||||
// ── Social ────────────────────────────────────────────────────────────
|
||||
FEED: { type: 'emoji', value: '📰' },
|
||||
@@ -55,12 +92,34 @@ window.ICONS = {
|
||||
LOGOUT: { type: 'emoji', value: '⏏️' },
|
||||
PROFILE: { type: 'emoji', value: '👤' },
|
||||
HELP: { type: 'emoji', value: '❓' },
|
||||
CONTACT: { type: 'emoji', value: '✉️' }, // Kontakt / E-Mail
|
||||
|
||||
// ── Medien / Dateien ──────────────────────────────────────────────────
|
||||
PHOTO: { type: 'emoji', value: '📷' }, // Foto / Kamera
|
||||
FILE_UPLOAD: { type: 'emoji', value: '📁' }, // Datei auswählen / Upload
|
||||
TEMPLATE: { type: 'emoji', value: '📋' }, // Vorlage / Template
|
||||
DOCUMENT: { type: 'emoji', value: '📄' }, // Dokument / Impressum
|
||||
GUIDE: { type: 'emoji', value: '📖' }, // Anleitung / Hilfeseite
|
||||
STATS: { type: 'emoji', value: '📊' }, // Statistik / Umfrage-Ergebnis
|
||||
PACKAGE: { type: 'emoji', value: '📦' }, // Paket / Einladung
|
||||
MAILBOX: { type: 'emoji', value: '📬' }, // Posteingang (Admin)
|
||||
|
||||
// ── Abo / Premium ─────────────────────────────────────────────────────
|
||||
PREMIUM: { type: 'emoji', value: '⭐' }, // Abonnement / Premium
|
||||
TROPHY: { type: 'emoji', value: '🏆' }, // Auszeichnung / Erfolg
|
||||
PAYMENT: { type: 'emoji', value: '💳' }, // Zahlung / Abonnement
|
||||
|
||||
// ── TTLock / Technik ──────────────────────────────────────────────────
|
||||
MOBILE: { type: 'emoji', value: '📱' }, // TTLock-App / Mobilgerät
|
||||
CONNECTION: { type: 'emoji', value: '🔌' }, // Verbindung / Integration
|
||||
GAMEPAD: { type: 'emoji', value: '🕹️' }, // Spielsteuerung
|
||||
SHIELD: { type: 'emoji', value: '🛡️' }, // Sicherheit / Datenschutz
|
||||
ADMIN_TOOLS: { type: 'emoji', value: '🔧' }, // Admin / Werkzeuge
|
||||
|
||||
// ── Aufgaben / Items ──────────────────────────────────────────────────
|
||||
TOYS: { type: 'emoji', value: '➰' },
|
||||
|
||||
// ── Spielhistorie – Spieltypen ────────────────────────────────────────
|
||||
// Einfache Spieltypen
|
||||
GAME_BDSM: { type: 'emoji', value: '⛓️' },
|
||||
GAME_VANILLA: { type: 'emoji', value: '❤️' },
|
||||
|
||||
|
||||
@@ -330,14 +330,24 @@
|
||||
function esc(s) { const d = document.createElement('div'); d.textContent = s ?? ''; return d.innerHTML; }
|
||||
|
||||
const lockDetailCache = {};
|
||||
const lockTypeMap = {}; // lockId → 'CARDLOCK' | 'TIMELOCK'
|
||||
|
||||
// ── Meine Locks als Keyholder ──
|
||||
|
||||
async function loadLocks() {
|
||||
try {
|
||||
const res = await fetch('/keyholder/as-keyholder');
|
||||
if (!res.ok) return;
|
||||
const locks = await res.json();
|
||||
const [clRes, tlRes] = await Promise.all([
|
||||
fetch('/keyholder/as-keyholder'),
|
||||
fetch('/keyholder/timelock/as-keyholder')
|
||||
]);
|
||||
const cardLocks = clRes.ok ? await clRes.json() : [];
|
||||
const timeLocks = tlRes.ok ? await tlRes.json() : [];
|
||||
const locks = [
|
||||
...cardLocks.map(l => ({ ...l, lockType: 'CARDLOCK' })),
|
||||
...timeLocks.map(l => ({ ...l, lockType: 'TIMELOCK' }))
|
||||
];
|
||||
locks.forEach(l => { lockTypeMap[l.lockId] = l.lockType; });
|
||||
|
||||
const grid = document.getElementById('locksGrid');
|
||||
grid.innerHTML = '';
|
||||
const empty = document.getElementById('locksEmpty');
|
||||
@@ -348,7 +358,11 @@
|
||||
? `<div class="lock-card-avatar"><img src="data:image/jpeg;base64,${l.lockeeProfilePic}" alt=""></div>`
|
||||
: `<div class="lock-card-avatar">👤</div>`;
|
||||
const startDate = l.startTime ? new Date(l.startTime).toLocaleDateString('de-DE') : '–';
|
||||
const frozenBadge = l.isFrozenByKeyholder ? ' · ❄️ Eingefroren' : '';
|
||||
const frozenBadge = (l.isFrozenByKeyholder || l.isFrozen) ? ' · ❄️ Eingefroren' : '';
|
||||
const emergencyBadge = l.emergencyUnlockRequested ? ' · 🆘 Notfall!' : '';
|
||||
const line3 = l.lockType === 'TIMELOCK'
|
||||
? `⏱ TimeLock · seit ${startDate}${frozenBadge}${emergencyBadge}`
|
||||
: `🃏 ${l.totalCards} Karten · seit ${startDate}${frozenBadge}${emergencyBadge}`;
|
||||
const card = document.createElement('div');
|
||||
card.className = 'lock-card';
|
||||
card.dataset.lockId = l.lockId;
|
||||
@@ -358,7 +372,7 @@
|
||||
<div class="lock-card-body">
|
||||
<div class="lock-card-line1">${esc(l.lockeeName)}</div>
|
||||
<div class="lock-card-line2">${esc(l.lockName)}</div>
|
||||
<div class="lock-card-line3">🃏 ${l.totalCards} Karten · seit ${startDate}${frozenBadge}</div>
|
||||
<div class="lock-card-line3">${line3}</div>
|
||||
</div>
|
||||
<span class="lock-toggle">▶</span>
|
||||
</div>
|
||||
@@ -382,9 +396,13 @@
|
||||
const card = document.querySelector(`[data-lock-id="${lockId}"]`);
|
||||
const body = card ? card.querySelector('.lock-detail-body') : null;
|
||||
try {
|
||||
const res = await fetch('/keyholder/as-keyholder/' + lockId);
|
||||
const endpoint = lockTypeMap[lockId] === 'TIMELOCK'
|
||||
? '/keyholder/timelock/as-keyholder/' + lockId
|
||||
: '/keyholder/as-keyholder/' + lockId;
|
||||
const res = await fetch(endpoint);
|
||||
if (!res.ok) { if (body) body.textContent = 'Fehler beim Laden.'; return; }
|
||||
const d = await res.json();
|
||||
lockTypeMap[lockId] = d.lockType || lockTypeMap[lockId] || 'CARDLOCK';
|
||||
lockDetailCache[lockId] = d;
|
||||
if (body) {
|
||||
body.innerHTML = buildDetailHtml(d);
|
||||
@@ -404,7 +422,90 @@
|
||||
}
|
||||
}
|
||||
|
||||
function buildTimeLockDetailHtml(d) {
|
||||
let html = `<div style="margin-bottom:0.75rem;">
|
||||
<a href="/benutzer.html?userId=${d.lockeeId}" style="font-size:0.82rem;color:var(--color-primary);">Profil ansehen →</a>
|
||||
</div>`;
|
||||
|
||||
// Zeitinfo
|
||||
html += `<div class="detail-section"><div class="detail-section-title">TimeLock</div>`;
|
||||
if (d.timeUp) {
|
||||
html += `<div class="detail-row"><span class="detail-label">Status</span><span class="detail-value ok">✅ Entsperrbereit</span></div>`;
|
||||
} else if (d.unlockTime) {
|
||||
html += `<div class="detail-row">
|
||||
<span class="detail-label">Entsperrt um</span>
|
||||
<span class="detail-value">${new Date(d.unlockTime).toLocaleString('de-DE')}</span>
|
||||
</div>`;
|
||||
} else {
|
||||
html += `<div class="detail-row"><span class="detail-label">Entsperrzeit</span><span class="detail-value" style="color:var(--color-muted);">Nicht sichtbar</span></div>`;
|
||||
}
|
||||
if (d.isFrozen) {
|
||||
const fu = d.frozenUntil ? new Date(d.frozenUntil).toLocaleString('de-DE') : 'unlimitiert';
|
||||
html += `<div class="detail-row"><span class="detail-label">❄️ Eingefroren bis</span><span class="detail-value danger">${fu}</span></div>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
|
||||
// Verifikation
|
||||
if (d.requiresVerification) {
|
||||
html += `<div class="detail-section"><div class="detail-section-title">Verifikation heute</div>`;
|
||||
if (!d.verificationDoneToday) {
|
||||
html += `<div class="detail-row"><span class="detail-label">Status</span><span class="detail-value danger">Warte auf Verifikation</span></div>`;
|
||||
} else if (!d.verificationMyVote) {
|
||||
html += `<div class="detail-row">
|
||||
<span class="detail-label">Status</span>
|
||||
<span class="detail-value warn">Ausstehend <a href="#" class="prufen-link" style="font-size:0.82rem;color:var(--color-primary);font-weight:600;">Prüfen →</a></span>
|
||||
</div>`;
|
||||
} else if (d.verificationMyVote === 'upvote') {
|
||||
html += `<div class="detail-row"><span class="detail-label">Status</span><span class="detail-value ok">✓ Erledigt</span></div>`;
|
||||
} else {
|
||||
html += `<div class="detail-row"><span class="detail-label">Status</span><span class="detail-value danger">✗ Abgelehnt</span></div>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
}
|
||||
|
||||
// Gestartet am
|
||||
if (d.startTime) {
|
||||
html += `<div style="font-size:0.78rem;color:var(--color-muted);margin-top:0.5rem;">
|
||||
Gestartet am ${new Date(d.startTime).toLocaleDateString('de-DE')}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Notfall-Banner
|
||||
if (d.emergencyUnlockRequested && !d.keyholderRequestedUnlock) {
|
||||
html += `<div style="display:flex;align-items:flex-start;gap:0.75rem;background:rgba(231,76,60,0.12);border:2px solid rgba(231,76,60,0.5);border-radius:10px;padding:1rem 1.1rem;margin-top:0.75rem;margin-bottom:0.5rem;">
|
||||
<span style="font-size:1.4rem;flex-shrink:0;">🆘</span>
|
||||
<div>
|
||||
<div style="font-weight:700;font-size:0.95rem;color:#e74c3c;margin-bottom:0.25rem;">Notfall-Entsperrung angefordert!</div>
|
||||
<div style="font-size:0.85rem;color:var(--color-muted);line-height:1.5;">Deine Lockee bittet dringend um Freigabe des Locks. Reagiere innerhalb einer Stunde oder das Lock öffnet sich automatisch.</div>
|
||||
<div style="margin-top:0.6rem;">
|
||||
<button onclick="requestUnlock('${d.lockId}')"
|
||||
style="background:rgba(46,204,113,0.2);border:1px solid rgba(46,204,113,0.5);color:#2ecc71;padding:0.4rem 0.9rem;border-radius:7px;cursor:pointer;font-size:0.85rem;font-weight:600;width:auto;">
|
||||
🔓 Jetzt freigeben
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Lock entsperren
|
||||
html += `<div class="detail-section"><div class="detail-section-title">Lock entsperren</div>`;
|
||||
if (d.keyholderRequestedUnlock) {
|
||||
html += `<div style="font-size:0.85rem;color:var(--color-muted);">✅ Unlock wurde angefordert – die Lockee erhält beim nächsten Laden ihren Entsperrcode.</div>`;
|
||||
} else {
|
||||
html += `<div id="unlockConfirm_${d.lockId}">
|
||||
<button onclick="showUnlockConfirm('${d.lockId}')"
|
||||
style="background:rgba(46,204,113,0.15);border:1px solid rgba(46,204,113,0.4);color:#2ecc71;padding:0.4rem 0.9rem;border-radius:7px;cursor:pointer;font-size:0.82rem;font-weight:600;width:auto;">
|
||||
🔓 Lock freigeben
|
||||
</button>
|
||||
</div>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function buildDetailHtml(d) {
|
||||
if (d.lockType === 'TIMELOCK') return buildTimeLockDetailHtml(d);
|
||||
let html = `<div style="margin-bottom:0.75rem;">
|
||||
<a href="/benutzer.html?userId=${d.lockeeId}" style="font-size:0.82rem;color:var(--color-primary);">Profil ansehen →</a>
|
||||
</div>`;
|
||||
@@ -1103,7 +1204,10 @@
|
||||
|
||||
async function requestUnlock(lockId) {
|
||||
try {
|
||||
const res = await fetch(`/keyholder/as-keyholder/${lockId}/request-unlock`, { method: 'POST' });
|
||||
const endpoint = (lockDetailCache[lockId]?.lockType || lockTypeMap[lockId]) === 'TIMELOCK'
|
||||
? `/keyholder/timelock/as-keyholder/${lockId}/request-unlock`
|
||||
: `/keyholder/as-keyholder/${lockId}/request-unlock`;
|
||||
const res = await fetch(endpoint, { method: 'POST' });
|
||||
if (res.ok || res.status === 204) {
|
||||
await reloadLockDetail(lockId);
|
||||
}
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
display:flex; align-items:center; gap:0.5rem;
|
||||
background:var(--color-card); border-radius:7px; padding:0.55rem 0.75rem;
|
||||
flex-wrap:wrap;
|
||||
border-left: 4px solid transparent; transition: border-color 0.15s;
|
||||
}
|
||||
.wheel-item select {
|
||||
flex:1; min-width:150px; box-sizing:border-box;
|
||||
@@ -650,6 +651,15 @@
|
||||
{ value:'TASK', label:'Aufgabe zuweisen', hasInt:false, hasStr:false },
|
||||
{ value:'TEXT', label:'Text anzeigen', hasInt:false, hasStr:true, strLabel:'Text' },
|
||||
];
|
||||
const WHEEL_TYPE_COLORS = {
|
||||
ADD_TIME: '#f39c12',
|
||||
REMOVE_TIME: '#27ae60',
|
||||
FREEZE_TIME: '#3498db',
|
||||
FREEZE: '#e74c3c',
|
||||
UNFREEZE: '#27ae60',
|
||||
TASK: '#e6b800',
|
||||
TEXT: '#3498db',
|
||||
};
|
||||
|
||||
function addWheelEntry(data) {
|
||||
const id = ++wheelCtr;
|
||||
@@ -684,8 +694,10 @@
|
||||
const sel = document.querySelector(`#we-${id} select`);
|
||||
if (!sel) return;
|
||||
const def = WHEEL_TYPES.find(t => t.value === sel.value) || WHEEL_TYPES[0];
|
||||
document.getElementById('we-tp-' + id).style.display = def.hasInt ? '' : 'none';
|
||||
document.getElementById('we-str-' + id).style.display = def.hasStr ? '' : 'none';
|
||||
document.getElementById('we-tp-' + id).style.display = def.hasInt ? '' : 'none';
|
||||
document.getElementById('we-str-' + id).style.display = def.hasStr ? '' : 'none';
|
||||
const item = document.getElementById('we-' + id);
|
||||
if (item) item.style.borderLeftColor = WHEEL_TYPE_COLORS[sel.value] || 'transparent';
|
||||
}
|
||||
function removeWheelEntry(id) {
|
||||
document.getElementById('we-' + id)?.remove();
|
||||
|
||||
@@ -701,6 +701,38 @@
|
||||
return cards;
|
||||
}
|
||||
|
||||
// ── Plausibilitätsprüfung für TimeLock ──
|
||||
function validateTimeLockPlausibility(t) {
|
||||
const errors = [];
|
||||
const hasTasks = t.tasks && t.tasks.length > 0;
|
||||
const spinEntries = t.spinningWheelEntries || [];
|
||||
|
||||
// Spinning Wheel enthält Task-Felder, aber keine Aufgaben definiert
|
||||
if (spinEntries.some(e => e.type === 'TASK') && !hasTasks) {
|
||||
errors.push('Das Spinning Wheel enthält Aufgaben-Felder (TASK), aber die Vorlage hat keine Aufgaben definiert. Bitte die Vorlage bearbeiten.');
|
||||
}
|
||||
|
||||
// Aufgaben-Häufigkeit konfiguriert, aber keine Aufgaben vorhanden
|
||||
if ((t.taskEveryMinutes > 0 || t.minTasksPerDay > 0) && !hasTasks) {
|
||||
errors.push('Aufgaben sind zeitlich konfiguriert, aber keine Aufgaben in der Vorlage definiert. Bitte die Vorlage bearbeiten.');
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
function showPlausibilityErrors(errors) {
|
||||
const el = document.getElementById('errorMsg');
|
||||
if (errors.length === 1) {
|
||||
el.textContent = errors[0];
|
||||
} else {
|
||||
el.innerHTML = 'Die Vorlage enthält inkonsistente Einstellungen:<ul style="margin:0.4rem 0 0 1.2rem;padding:0;">'
|
||||
+ errors.map(e => `<li>${e}</li>`).join('')
|
||||
+ '</ul>';
|
||||
}
|
||||
el.style.display = '';
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
|
||||
// ── Absenden ──
|
||||
async function createSession() {
|
||||
document.getElementById('errorMsg').style.display = 'none';
|
||||
@@ -716,6 +748,14 @@
|
||||
const t = selectedTemplate || allTemplates.find(x => x.templateId === templateId);
|
||||
if (!t) { showError('Vorlage nicht gefunden.'); return; }
|
||||
|
||||
if (t._type === 'timelock') {
|
||||
const plausErrors = validateTimeLockPlausibility(t);
|
||||
if (plausErrors.length > 0) {
|
||||
showPlausibilityErrors(plausErrors);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const lockeeVal = document.getElementById('lockeeValue').value;
|
||||
const keyholderVal = document.getElementById('keyholderValue').value;
|
||||
const isFriendLockee = lockeeVal && lockeeVal !== myUserId;
|
||||
|
||||
Reference in New Issue
Block a user