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(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(git -C /home/mario/Workspaces/xxx-thegame diff HEAD xxxthegame/src/main/resources/static/neulock.html)",
|
||||||
"Bash(1:*)",
|
"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
|
display=\:0
|
||||||
host=mario-mint
|
host=mario-mint
|
||||||
process-id=4033
|
process-id=43084
|
||||||
user=mario
|
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
|
!ENTRY org.springframework.tooling.boot.ls 1 0 2026-03-25 16:28:18.002
|
||||||
!MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS
|
!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 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-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 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.core.runtime=2
|
||||||
org.eclipse.platform=4.39.0.v20260226-0420
|
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
|
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
|
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
|
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
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "current_lock")
|
@Table(name = "active_lock")
|
||||||
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
|
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
|
||||||
@DiscriminatorColumn(name = "lock_type", discriminatorType = DiscriminatorType.STRING)
|
@DiscriminatorColumn(name = "lock_type", discriminatorType = DiscriminatorType.STRING)
|
||||||
public class BaseLockEntity {
|
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.lockee.LockeeInvitationRepository;
|
||||||
import de.oaa.xxx.games.chastity.spinningwheel.SpinningWheelEntry;
|
import de.oaa.xxx.games.chastity.spinningwheel.SpinningWheelEntry;
|
||||||
import de.oaa.xxx.games.chastity.unlock.TempOpeningReason;
|
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.social.SystemMessageService;
|
||||||
import de.oaa.xxx.user.UserRepository;
|
import de.oaa.xxx.user.UserRepository;
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@ public class TimeLockController {
|
|||||||
private final CommunityVerificationRepository verificationRepository;
|
private final CommunityVerificationRepository verificationRepository;
|
||||||
private final CommunityVerificationVoteRepository verificationVoteRepository;
|
private final CommunityVerificationVoteRepository verificationVoteRepository;
|
||||||
private final SubscriptionLimitService subscriptionLimitService;
|
private final SubscriptionLimitService subscriptionLimitService;
|
||||||
|
private final UnlockCodeHistoryService unlockCodeHistoryService;
|
||||||
|
|
||||||
public TimeLockController(TimeLockRepository timeLockRepository,
|
public TimeLockController(TimeLockRepository timeLockRepository,
|
||||||
TimeLockTemplateRepository templateRepository,
|
TimeLockTemplateRepository templateRepository,
|
||||||
@@ -69,7 +71,8 @@ public class TimeLockController {
|
|||||||
TimeLockServiceFactory timeLockServiceFactory,
|
TimeLockServiceFactory timeLockServiceFactory,
|
||||||
CommunityVerificationRepository verificationRepository,
|
CommunityVerificationRepository verificationRepository,
|
||||||
CommunityVerificationVoteRepository verificationVoteRepository,
|
CommunityVerificationVoteRepository verificationVoteRepository,
|
||||||
SubscriptionLimitService subscriptionLimitService) {
|
SubscriptionLimitService subscriptionLimitService,
|
||||||
|
UnlockCodeHistoryService unlockCodeHistoryService) {
|
||||||
this.timeLockRepository = timeLockRepository;
|
this.timeLockRepository = timeLockRepository;
|
||||||
this.templateRepository = templateRepository;
|
this.templateRepository = templateRepository;
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
@@ -80,6 +83,7 @@ public class TimeLockController {
|
|||||||
this.verificationRepository = verificationRepository;
|
this.verificationRepository = verificationRepository;
|
||||||
this.verificationVoteRepository = verificationVoteRepository;
|
this.verificationVoteRepository = verificationVoteRepository;
|
||||||
this.subscriptionLimitService = subscriptionLimitService;
|
this.subscriptionLimitService = subscriptionLimitService;
|
||||||
|
this.unlockCodeHistoryService = unlockCodeHistoryService;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Erstellen ────────────────────────────────────────────────────────────────
|
// ── Erstellen ────────────────────────────────────────────────────────────────
|
||||||
@@ -357,7 +361,7 @@ public class TimeLockController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.put("keyholderRequestedUnlock", l.isKeyholderRequestedUnlock());
|
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("unlockCode", l.getUnlockCode() != null ? l.getUnlockCode() : "");
|
||||||
}
|
}
|
||||||
result.put("emergencyUnlockRequested", l.getEmergencyUnlockRequestedAt() != null);
|
result.put("emergencyUnlockRequested", l.getEmergencyUnlockRequestedAt() != null);
|
||||||
@@ -584,6 +588,10 @@ public class TimeLockController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TimeLockService service = timeLockServiceFactory.create(l);
|
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());
|
service.unlock(l.getUnlockCode());
|
||||||
|
|
||||||
// Clean up verifications
|
// Clean up verifications
|
||||||
@@ -596,6 +604,123 @@ public class TimeLockController {
|
|||||||
return ResponseEntity.noContent().build();
|
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 ───────────────────────────────────────────────────────
|
// ── Notfall-Entsperrung ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
@PostMapping("/timelock/{lockId}/emergency-unlock")
|
@PostMapping("/timelock/{lockId}/emergency-unlock")
|
||||||
@@ -615,15 +740,21 @@ public class TimeLockController {
|
|||||||
|
|
||||||
l.setEmergencyUnlockRequestedAt(LocalDateTime.now());
|
l.setEmergencyUnlockRequestedAt(LocalDateTime.now());
|
||||||
if (l.getKeyholder() == null) {
|
if (l.getKeyholder() == null) {
|
||||||
|
// Kein Keyholder: sofort entsperren, Lock als ungültig markieren (keine Historie, keine XP)
|
||||||
l.setEmergencyAutoUnlocked(true);
|
l.setEmergencyAutoUnlocked(true);
|
||||||
l.setKeyholderRequestedUnlock(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 {
|
} else {
|
||||||
systemMessageService.send(myId, l.getKeyholder(),
|
systemMessageService.send(myId, l.getKeyholder(),
|
||||||
"⚠️ NOTFALL: " + me.getName() + " bittet dringend um Freigabe des Locks. Bitte reagiere innerhalb einer Stunde.",
|
"⚠️ NOTFALL: " + me.getName() + " bittet dringend um Freigabe des Locks. Bitte reagiere innerhalb einer Stunde.",
|
||||||
"/keyholder.html", de.oaa.xxx.social.entity.MessageCause.EMERGENCY);
|
"/keyholder.html", de.oaa.xxx.social.entity.MessageCause.EMERGENCY);
|
||||||
|
timeLockRepository.save(l);
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
timeLockRepository.save(l);
|
|
||||||
return ResponseEntity.noContent().build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Hilfsmethoden ─────────────────────────────────────────────────────────────
|
// ── Hilfsmethoden ─────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package de.oaa.xxx.games.chastity.timelock;
|
package de.oaa.xxx.games.chastity.timelock;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
@@ -10,7 +11,9 @@ public interface TimeLockRepository extends JpaRepository<TimeLockEntity, UUID>
|
|||||||
|
|
||||||
boolean existsByLockeeAndStartTimeIsNotNullAndUnlockTimeIsNull(UUID lockee);
|
boolean existsByLockeeAndStartTimeIsNotNullAndUnlockTimeIsNull(UUID lockee);
|
||||||
|
|
||||||
@Modifying
|
List<TimeLockEntity> findByKeyholderAndStartTimeIsNotNullAndUnlockTimeIsNull(UUID keyholder);
|
||||||
|
|
||||||
|
@Modifying(clearAutomatically = true)
|
||||||
@Query("DELETE FROM TimeLockEntity t WHERE t.lockId = :lockId")
|
@Query("DELETE FROM TimeLockEntity t WHERE t.lockId = :lockId")
|
||||||
void deleteByLockId(UUID lockId);
|
void deleteByLockId(UUID lockId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1208,26 +1208,9 @@
|
|||||||
async function lockLoeschen() {
|
async function lockLoeschen() {
|
||||||
closeWarnModal();
|
closeWarnModal();
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/keyholder/timelock/' + lockId, { method: 'DELETE' });
|
await fetch('/keyholder/timelock/' + lockId, { method: 'DELETE' });
|
||||||
if (res.ok) {
|
} catch(_) { /* ignorieren */ }
|
||||||
const data = await res.json().catch(() => ({}));
|
window.location.href = '/userhome.html';
|
||||||
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';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openEmergencyModal() {
|
function openEmergencyModal() {
|
||||||
@@ -1253,13 +1236,30 @@
|
|||||||
document.getElementById('emergencyModalActions').style.display = 'none';
|
document.getElementById('emergencyModalActions').style.display = 'none';
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/keyholder/timelock/' + lockId + '/emergency-unlock', { method: 'POST' });
|
const res = await fetch('/keyholder/timelock/' + lockId + '/emergency-unlock', { method: 'POST' });
|
||||||
if (res.ok || res.status === 204) {
|
if (res.status === 200) {
|
||||||
const hasKH2 = _currentLock && _currentLock.keyholderName;
|
// Kein Keyholder: Entsperrcode direkt anzeigen, Lock als ungültig markiert
|
||||||
const successText = hasKH2
|
const data = await res.json().catch(() => ({}));
|
||||||
? `✅ Notfall-Anfrage gesendet. ${_currentLock.keyholderName} wurde benachrichtigt.`
|
document.getElementById('emergencyModalContent').innerHTML = `
|
||||||
: `✅ Notfall-Freigabe ausgelöst. Das Lock öffnet sich jetzt.`;
|
<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 =
|
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);
|
setTimeout(() => { closeEmergencyModal(); loadLock(); }, 2000);
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ window.ICONS = {
|
|||||||
CHECK: { type: 'emoji', value: '✅' },
|
CHECK: { type: 'emoji', value: '✅' },
|
||||||
DISCOVER: { type: 'emoji', value: '🗺️' },
|
DISCOVER: { type: 'emoji', value: '🗺️' },
|
||||||
ARROW: { 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 ────────────────────────────────────────────────────────
|
// ── UI-Symbole ────────────────────────────────────────────────────────
|
||||||
CLOSE: { type: 'symbol', value: '✕' }, // Schließen / Ablehnen / Löschen
|
CLOSE: { type: 'symbol', value: '✕' }, // Schließen / Ablehnen / Löschen
|
||||||
@@ -35,13 +38,47 @@ window.ICONS = {
|
|||||||
LIGHTNING: { type: 'emoji', value: '⚡' }, // Aktion (z. B. Zeit entfernen)
|
LIGHTNING: { type: 'emoji', value: '⚡' }, // Aktion (z. B. Zeit entfernen)
|
||||||
EMOJI_PICKER: { type: 'emoji', value: '😊' }, // Emoji-Picker öffnen
|
EMOJI_PICKER: { type: 'emoji', value: '😊' }, // Emoji-Picker öffnen
|
||||||
REMOVE: { type: 'symbol', value: '⊗' }, // Eintrag/Spiel entfernen
|
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 ─────────────────────────────────────────────────────
|
// ── Chastity Game ─────────────────────────────────────────────────────
|
||||||
NEW_LOCK: { type: 'emoji', value: '🆕' },
|
NEW_LOCK: { type: 'emoji', value: '🆕' },
|
||||||
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: '🔑' },
|
KEY: { type: 'emoji', value: '🔑' },
|
||||||
HISTORY: { type: 'emoji', value: '🔙' },
|
HISTORY: { type: 'emoji', value: '🔙' },
|
||||||
VOTES: { 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 ────────────────────────────────────────────────────────────
|
// ── Social ────────────────────────────────────────────────────────────
|
||||||
FEED: { type: 'emoji', value: '📰' },
|
FEED: { type: 'emoji', value: '📰' },
|
||||||
@@ -55,12 +92,34 @@ window.ICONS = {
|
|||||||
LOGOUT: { type: 'emoji', value: '⏏️' },
|
LOGOUT: { type: 'emoji', value: '⏏️' },
|
||||||
PROFILE: { type: 'emoji', value: '👤' },
|
PROFILE: { type: 'emoji', value: '👤' },
|
||||||
HELP: { 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 ──────────────────────────────────────────────────
|
// ── Aufgaben / Items ──────────────────────────────────────────────────
|
||||||
TOYS: { type: 'emoji', value: '➰' },
|
TOYS: { type: 'emoji', value: '➰' },
|
||||||
|
|
||||||
// ── Spielhistorie – Spieltypen ────────────────────────────────────────
|
// ── Spielhistorie – Spieltypen ────────────────────────────────────────
|
||||||
// Einfache Spieltypen
|
|
||||||
GAME_BDSM: { type: 'emoji', value: '⛓️' },
|
GAME_BDSM: { type: 'emoji', value: '⛓️' },
|
||||||
GAME_VANILLA: { 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; }
|
function esc(s) { const d = document.createElement('div'); d.textContent = s ?? ''; return d.innerHTML; }
|
||||||
|
|
||||||
const lockDetailCache = {};
|
const lockDetailCache = {};
|
||||||
|
const lockTypeMap = {}; // lockId → 'CARDLOCK' | 'TIMELOCK'
|
||||||
|
|
||||||
// ── Meine Locks als Keyholder ──
|
// ── Meine Locks als Keyholder ──
|
||||||
|
|
||||||
async function loadLocks() {
|
async function loadLocks() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/keyholder/as-keyholder');
|
const [clRes, tlRes] = await Promise.all([
|
||||||
if (!res.ok) return;
|
fetch('/keyholder/as-keyholder'),
|
||||||
const locks = await res.json();
|
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');
|
const grid = document.getElementById('locksGrid');
|
||||||
grid.innerHTML = '';
|
grid.innerHTML = '';
|
||||||
const empty = document.getElementById('locksEmpty');
|
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"><img src="data:image/jpeg;base64,${l.lockeeProfilePic}" alt=""></div>`
|
||||||
: `<div class="lock-card-avatar">👤</div>`;
|
: `<div class="lock-card-avatar">👤</div>`;
|
||||||
const startDate = l.startTime ? new Date(l.startTime).toLocaleDateString('de-DE') : '–';
|
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');
|
const card = document.createElement('div');
|
||||||
card.className = 'lock-card';
|
card.className = 'lock-card';
|
||||||
card.dataset.lockId = l.lockId;
|
card.dataset.lockId = l.lockId;
|
||||||
@@ -358,7 +372,7 @@
|
|||||||
<div class="lock-card-body">
|
<div class="lock-card-body">
|
||||||
<div class="lock-card-line1">${esc(l.lockeeName)}</div>
|
<div class="lock-card-line1">${esc(l.lockeeName)}</div>
|
||||||
<div class="lock-card-line2">${esc(l.lockName)}</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>
|
</div>
|
||||||
<span class="lock-toggle">▶</span>
|
<span class="lock-toggle">▶</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -382,9 +396,13 @@
|
|||||||
const card = document.querySelector(`[data-lock-id="${lockId}"]`);
|
const card = document.querySelector(`[data-lock-id="${lockId}"]`);
|
||||||
const body = card ? card.querySelector('.lock-detail-body') : null;
|
const body = card ? card.querySelector('.lock-detail-body') : null;
|
||||||
try {
|
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; }
|
if (!res.ok) { if (body) body.textContent = 'Fehler beim Laden.'; return; }
|
||||||
const d = await res.json();
|
const d = await res.json();
|
||||||
|
lockTypeMap[lockId] = d.lockType || lockTypeMap[lockId] || 'CARDLOCK';
|
||||||
lockDetailCache[lockId] = d;
|
lockDetailCache[lockId] = d;
|
||||||
if (body) {
|
if (body) {
|
||||||
body.innerHTML = buildDetailHtml(d);
|
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) {
|
function buildDetailHtml(d) {
|
||||||
|
if (d.lockType === 'TIMELOCK') return buildTimeLockDetailHtml(d);
|
||||||
let html = `<div style="margin-bottom:0.75rem;">
|
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>
|
<a href="/benutzer.html?userId=${d.lockeeId}" style="font-size:0.82rem;color:var(--color-primary);">Profil ansehen →</a>
|
||||||
</div>`;
|
</div>`;
|
||||||
@@ -1103,7 +1204,10 @@
|
|||||||
|
|
||||||
async function requestUnlock(lockId) {
|
async function requestUnlock(lockId) {
|
||||||
try {
|
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) {
|
if (res.ok || res.status === 204) {
|
||||||
await reloadLockDetail(lockId);
|
await reloadLockDetail(lockId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,6 +175,7 @@
|
|||||||
display:flex; align-items:center; gap:0.5rem;
|
display:flex; align-items:center; gap:0.5rem;
|
||||||
background:var(--color-card); border-radius:7px; padding:0.55rem 0.75rem;
|
background:var(--color-card); border-radius:7px; padding:0.55rem 0.75rem;
|
||||||
flex-wrap:wrap;
|
flex-wrap:wrap;
|
||||||
|
border-left: 4px solid transparent; transition: border-color 0.15s;
|
||||||
}
|
}
|
||||||
.wheel-item select {
|
.wheel-item select {
|
||||||
flex:1; min-width:150px; box-sizing:border-box;
|
flex:1; min-width:150px; box-sizing:border-box;
|
||||||
@@ -650,6 +651,15 @@
|
|||||||
{ value:'TASK', label:'Aufgabe zuweisen', hasInt:false, hasStr:false },
|
{ value:'TASK', label:'Aufgabe zuweisen', hasInt:false, hasStr:false },
|
||||||
{ value:'TEXT', label:'Text anzeigen', hasInt:false, hasStr:true, strLabel:'Text' },
|
{ 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) {
|
function addWheelEntry(data) {
|
||||||
const id = ++wheelCtr;
|
const id = ++wheelCtr;
|
||||||
@@ -684,8 +694,10 @@
|
|||||||
const sel = document.querySelector(`#we-${id} select`);
|
const sel = document.querySelector(`#we-${id} select`);
|
||||||
if (!sel) return;
|
if (!sel) return;
|
||||||
const def = WHEEL_TYPES.find(t => t.value === sel.value) || WHEEL_TYPES[0];
|
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-tp-' + id).style.display = def.hasInt ? '' : 'none';
|
||||||
document.getElementById('we-str-' + id).style.display = def.hasStr ? '' : '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) {
|
function removeWheelEntry(id) {
|
||||||
document.getElementById('we-' + id)?.remove();
|
document.getElementById('we-' + id)?.remove();
|
||||||
|
|||||||
@@ -701,6 +701,38 @@
|
|||||||
return cards;
|
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 ──
|
// ── Absenden ──
|
||||||
async function createSession() {
|
async function createSession() {
|
||||||
document.getElementById('errorMsg').style.display = 'none';
|
document.getElementById('errorMsg').style.display = 'none';
|
||||||
@@ -716,6 +748,14 @@
|
|||||||
const t = selectedTemplate || allTemplates.find(x => x.templateId === templateId);
|
const t = selectedTemplate || allTemplates.find(x => x.templateId === templateId);
|
||||||
if (!t) { showError('Vorlage nicht gefunden.'); return; }
|
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 lockeeVal = document.getElementById('lockeeValue').value;
|
||||||
const keyholderVal = document.getElementById('keyholderValue').value;
|
const keyholderVal = document.getElementById('keyholderValue').value;
|
||||||
const isFriendLockee = lockeeVal && lockeeVal !== myUserId;
|
const isFriendLockee = lockeeVal && lockeeVal !== myUserId;
|
||||||
|
|||||||
Reference in New Issue
Block a user