Änderungen am Msessage System, Datenschutz-Einstellungen hinzugefügt, BDSM und CardLock Game weiterverfeinert
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
package de.oaa.xxx.aufgaben;
|
||||
|
||||
import de.oaa.xxx.session.GeschlechtEnum;
|
||||
import de.oaa.xxx.games.bdsm.GeschlechtEnum;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
|
||||
@@ -67,9 +67,9 @@ public class AufgabeEntity {
|
||||
public Aufgabe toAufgabe() {
|
||||
Aufgabe aufgabe = new Aufgabe();
|
||||
aufgabe.setAufgabeId(aufgabeId);
|
||||
aufgabe.setBenoetigtAktiv(benoetigtAktiv);
|
||||
aufgabe.setBenoetigteToys(benoetigteToys.stream().map(ToyEntity::toToy).toList());
|
||||
aufgabe.setBenoetigtPassiv(benoetigtPassiv);
|
||||
aufgabe.setBenoetigtAktiv(benoetigtAktiv != null ? new ArrayList<>(benoetigtAktiv) : new ArrayList<>());
|
||||
aufgabe.setBenoetigteToys(benoetigteToys != null ? benoetigteToys.stream().map(ToyEntity::toToy).toList() : new ArrayList<>());
|
||||
aufgabe.setBenoetigtPassiv(benoetigtPassiv != null ? new ArrayList<>(benoetigtPassiv) : new ArrayList<>());
|
||||
aufgabe.setGruppeId(aufgabenGruppe.getGruppenId());
|
||||
aufgabe.setKurzText(kurzText);
|
||||
aufgabe.setLevel(level);
|
||||
|
||||
@@ -18,7 +18,7 @@ import java.util.UUID;
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "aufgabenGruppe")
|
||||
@Table(name = "aufgaben_gruppe")
|
||||
public class AufgabenGruppeEntity {
|
||||
|
||||
@Id
|
||||
|
||||
@@ -2,7 +2,7 @@ package de.oaa.xxx.aufgaben.entity;
|
||||
|
||||
import de.oaa.xxx.aufgaben.Finisher;
|
||||
import de.oaa.xxx.aufgaben.Werkzeug;
|
||||
import de.oaa.xxx.session.GeschlechtEnum;
|
||||
import de.oaa.xxx.games.bdsm.GeschlechtEnum;
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.CollectionTable;
|
||||
import jakarta.persistence.Column;
|
||||
@@ -67,9 +67,9 @@ public class FinisherEntity {
|
||||
finisher.setKurzText(kurzText);
|
||||
finisher.setText(text);
|
||||
finisher.setGeschlecht(geschlecht);
|
||||
finisher.setBenoetigtAktiv(benoetigtAktiv);
|
||||
finisher.setBenoetigtPassiv(benoetigtPassiv);
|
||||
finisher.setBenoetigteToys(benoetigteToys.stream().map(ToyEntity::toToy).toList());
|
||||
finisher.setBenoetigtAktiv(benoetigtAktiv != null ? new ArrayList<>(benoetigtAktiv) : new ArrayList<>());
|
||||
finisher.setBenoetigtPassiv(benoetigtPassiv != null ? new ArrayList<>(benoetigtPassiv) : new ArrayList<>());
|
||||
finisher.setBenoetigteToys(benoetigteToys != null ? benoetigteToys.stream().map(ToyEntity::toToy).toList() : new ArrayList<>());
|
||||
finisher.setGruppeId(aufgabenGruppe.getGruppenId());
|
||||
return finisher;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public class SperreEntity {
|
||||
sperre.setMinutenBis(minutenBis);
|
||||
sperre.setMinutenVon(minutenVon);
|
||||
sperre.setReleaseText(releaseText);
|
||||
sperre.setSperreFuer(sperreFuer);
|
||||
sperre.setSperreFuer(sperreFuer != null ? new ArrayList<>(sperreFuer) : new ArrayList<>());
|
||||
sperre.setText(text);
|
||||
sperre.setBenoetigteToys(benoetigteToys != null ? benoetigteToys.stream().map(ToyEntity::toToy).toList() : new ArrayList<>());
|
||||
return sperre;
|
||||
|
||||
@@ -67,9 +67,9 @@ public class StrafeEntity {
|
||||
public Strafe toStrafe() {
|
||||
Strafe strafe = new Strafe();
|
||||
strafe.setStrafeId(strafeId);
|
||||
strafe.setBenoetigtAktiv(benoetigtAktiv);
|
||||
strafe.setBenoetigteToys(benoetigteToys.stream().map(ToyEntity::toToy).toList());
|
||||
strafe.setBenoetigtPassiv(benoetigtPassiv);
|
||||
strafe.setBenoetigtAktiv(benoetigtAktiv != null ? new ArrayList<>(benoetigtAktiv) : new ArrayList<>());
|
||||
strafe.setBenoetigteToys(benoetigteToys != null ? benoetigteToys.stream().map(ToyEntity::toToy).toList() : new ArrayList<>());
|
||||
strafe.setBenoetigtPassiv(benoetigtPassiv != null ? new ArrayList<>(benoetigtPassiv) : new ArrayList<>());
|
||||
strafe.setGruppeId(aufgabenGruppe.getGruppenId());
|
||||
strafe.setKurzText(kurzText);
|
||||
strafe.setLevel(level);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.oaa.xxx.session;
|
||||
package de.oaa.xxx.games.bdsm;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
@@ -1,21 +1,25 @@
|
||||
package de.oaa.xxx.session;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class AufgabeAnzeige {
|
||||
|
||||
private String nameAktiverMitspieler;
|
||||
private String aufgabeText;
|
||||
private Integer timer;
|
||||
private Callback callback;
|
||||
private Integer level;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AufgabeAnzeige[mitspieler=" + nameAktiverMitspieler + ", level=" + level + ", timer=" + timer
|
||||
+ ", callback=" + (callback != null ? callback.getClass().getSimpleName() : null) + "]";
|
||||
}
|
||||
}
|
||||
package de.oaa.xxx.games.bdsm;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class AufgabeAnzeige {
|
||||
|
||||
private String nameAktiverMitspieler;
|
||||
private String aufgabeText;
|
||||
private Integer timer;
|
||||
private Callback callback;
|
||||
private Integer level;
|
||||
private UUID mitspielerId;
|
||||
private boolean eigenesGeraet;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AufgabeAnzeige[mitspieler=" + nameAktiverMitspieler + ", level=" + level + ", timer=" + timer
|
||||
+ ", callback=" + (callback != null ? callback.getClass().getSimpleName() : null) + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.oaa.xxx.session;
|
||||
package de.oaa.xxx.games.bdsm;
|
||||
|
||||
public enum AufgabeArt {
|
||||
AUFGABE,
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.oaa.xxx.session;
|
||||
package de.oaa.xxx.games.bdsm;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
@@ -8,10 +8,11 @@ import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class Session {
|
||||
public class BdsmGame {
|
||||
|
||||
private UUID sessionId;
|
||||
private UUID userId;
|
||||
private UUID setupId;
|
||||
private Integer wahrscheinlichkeitSperre;
|
||||
private Integer wahrscheinlichkeitStrafe;
|
||||
private Integer aufgabenProLevel;
|
||||
@@ -1,251 +1,265 @@
|
||||
package de.oaa.xxx.session;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import de.oaa.xxx.session.aufgaben.Aufgabe;
|
||||
import de.oaa.xxx.session.aufgaben.AufgabenList;
|
||||
import de.oaa.xxx.session.aufgaben.Sperre;
|
||||
import de.oaa.xxx.session.aufgaben.Strafe;
|
||||
import de.oaa.xxx.session.entity.SessionEntity;
|
||||
import de.oaa.xxx.session.sperre.SperreCallback;
|
||||
import de.oaa.xxx.session.sperre.SperrenVerlaengernCallback;
|
||||
|
||||
public class SessionDurchfuehren {
|
||||
|
||||
private final AufgabenList aufgabenList;
|
||||
private final List<Mitspieler> mitspieler = new ArrayList<>();
|
||||
private final List<AktiveSperre> aktiveSperren = new ArrayList<>();
|
||||
|
||||
private final Integer wahrscheinlichkeitSperre;
|
||||
private final Integer wahrscheinlichkeitStrafe;
|
||||
|
||||
private int aufgabenProLevel;
|
||||
private int level;
|
||||
private int aufgabenAufAktuellemLevel;
|
||||
|
||||
public SessionDurchfuehren(SessionEntity entity) throws Exception {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
aufgabenList = objectMapper.readValue(entity.getAufgaben(), AufgabenList.class);
|
||||
entity.getMitspieler().forEach(mitspielerEntity -> mitspieler.add(mitspielerEntity.toMitspieler()));
|
||||
entity.getAktiveSperren().forEach(sperreEntity -> aktiveSperren.add(sperreEntity.toSperre(mitspieler)));
|
||||
|
||||
wahrscheinlichkeitSperre = entity.getWahrscheinlichkeitSperre();
|
||||
wahrscheinlichkeitStrafe = entity.getWahrscheinlichkeitStrafe();
|
||||
|
||||
this.aufgabenProLevel = entity.getAufgabenProLevel() != null ? entity.getAufgabenProLevel() : 5;
|
||||
this.level = entity.getLevel() != null ? entity.getLevel() : 1;
|
||||
this.aufgabenAufAktuellemLevel = entity.getAufgabenAufAktuellemLevel() != null ? entity.getAufgabenAufAktuellemLevel() : 0;
|
||||
}
|
||||
|
||||
public AufgabeAnzeige getNext() {
|
||||
checkLevel();
|
||||
if (level == 6) {
|
||||
return null;
|
||||
}
|
||||
AufgabeAnzeige anzeige = null;
|
||||
int nextInt = new Random().nextInt(1, 100);
|
||||
if (nextInt == 1) {
|
||||
anzeige = findUltimativeStrafe();
|
||||
} else if (nextInt == 2) {
|
||||
anzeige = findSperreVerlaengern();
|
||||
} else if (nextInt > wahrscheinlichkeitSperre + wahrscheinlichkeitStrafe + 2) {
|
||||
anzeige = findeAufgabe();
|
||||
} else if (nextInt > wahrscheinlichkeitSperre + 2) {
|
||||
anzeige = findeStrafe();
|
||||
} else {
|
||||
anzeige = findeSperre();
|
||||
}
|
||||
if (anzeige == null) {
|
||||
Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.AUFGABE_AKTIV);
|
||||
Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.AUFGABE_PASSIV, aktiv);
|
||||
String text = "Ups, da ist etwas schief gelaufen. Keine potenzielle Aufgabe gefunden. Entweder seid ihr inzwischen so gut weggesperrt, dass wirklich keine Aufgaben mehr zur Verfügung stehen, oder uns ist ein Fehler unterlaufen. {AKTIV} und {PASSIV} überbrücken die Zeit mit ein wenig Petting.";
|
||||
anzeige = new AufgabeAnzeige();
|
||||
anzeige.setNameAktiverMitspieler(aktiv != null ? aktiv.getName() : "");
|
||||
anzeige.setAufgabeText(getAnzeigeText(text, aktiv != null ? aktiv.getName() : "?", passiv != null ? passiv.getName() : "?"));
|
||||
anzeige.setTimer(120);
|
||||
}
|
||||
return anzeige;
|
||||
}
|
||||
|
||||
public void backToLvl5() {
|
||||
this.level = 5;
|
||||
this.aufgabenAufAktuellemLevel = 0;
|
||||
}
|
||||
|
||||
public List<AufgabeAnzeige> getFinisher() {
|
||||
var list = new ArrayList<AufgabeAnzeige>();
|
||||
List.of(GeschlechtEnum.WEIBLICH, GeschlechtEnum.DIVERS, GeschlechtEnum.MAENNLICH).forEach(geschlecht -> {
|
||||
mitspieler.stream().filter(m -> geschlecht == m.getGeschlecht()).toList().forEach(cumming -> {
|
||||
var partner = findeMitspielerMitRolle(RolleEnum.AUFGABE_PASSIV, cumming);
|
||||
var finishers = aufgabenList.getFinisher().stream()
|
||||
.filter(finisher -> geschlecht == finisher.getGeschlecht())
|
||||
.toList();
|
||||
if (!finishers.isEmpty()) {
|
||||
var aufgabe = finishers.get(new Random().nextInt(list.size()));
|
||||
AufgabeAnzeige anzeige = new AufgabeAnzeige();
|
||||
anzeige.setNameAktiverMitspieler(cumming.getName());
|
||||
anzeige.setAufgabeText(getAnzeigeText(aufgabe.getText(),
|
||||
cumming.getName(), partner != null ? partner.getName() : ""));
|
||||
list.add(anzeige);
|
||||
}
|
||||
});
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
private void checkLevel() {
|
||||
if (++aufgabenAufAktuellemLevel >= 1 + aufgabenProLevel) {
|
||||
aufgabenAufAktuellemLevel = 0;
|
||||
level++;
|
||||
}
|
||||
}
|
||||
|
||||
private AufgabeAnzeige findUltimativeStrafe() {
|
||||
Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_AKTIV);
|
||||
if (aktiv != null) {
|
||||
Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_PASSIV, aktiv);
|
||||
if (passiv != null) {
|
||||
String text = "{AKTIV}, verschnüre {PASSIV} fachmännisch inkl. KG, Plugs, Knebel, Augenbinde und was dir sonst einfällt. Nutze die Ruhe für was auch immer du möchtest.";
|
||||
AufgabeAnzeige anzeige = new AufgabeAnzeige();
|
||||
anzeige.setNameAktiverMitspieler(aktiv.getName());
|
||||
anzeige.setAufgabeText(getAnzeigeText(text, aktiv.getName(), passiv.getName()));
|
||||
anzeige.setTimer(new Random().nextInt(1800, 7200));
|
||||
return anzeige;
|
||||
}
|
||||
}
|
||||
return findeStrafe();
|
||||
}
|
||||
|
||||
private AufgabeAnzeige findSperreVerlaengern() {
|
||||
if (!aktiveSperren.isEmpty()) {
|
||||
AktiveSperre sperre = aktiveSperren.get(new Random().nextInt(aktiveSperren.size()));
|
||||
Mitspieler passiv = sperre.getMitspieler();
|
||||
Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_AKTIV, passiv);
|
||||
if (aktiv != null) {
|
||||
String text = "{AKTIV}, du entscheidest. Sollen alle bestehenden Zeitstrafen von {PASSIV} verlängert werden...?";
|
||||
AufgabeAnzeige anzeige = new AufgabeAnzeige();
|
||||
anzeige.setAufgabeText(getAnzeigeText(text, aktiv.getName(), passiv.getName()));
|
||||
anzeige.setNameAktiverMitspieler(aktiv.getName());
|
||||
SperrenVerlaengernCallback callback = new SperrenVerlaengernCallback();
|
||||
callback.setFaktor(new Random().nextInt(2, 4));
|
||||
callback.setSpielerId(passiv.getId());
|
||||
anzeige.setCallback(callback);
|
||||
return anzeige;
|
||||
}
|
||||
}
|
||||
return findeSperre();
|
||||
}
|
||||
|
||||
private AufgabeAnzeige findeAufgabe() {
|
||||
Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.AUFGABE_AKTIV);
|
||||
if (aktiv != null) {
|
||||
Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.AUFGABE_PASSIV, aktiv);
|
||||
if (passiv != null) {
|
||||
List<Aufgabe> list = aufgabenList.getAufgaben().stream()
|
||||
.filter(aufgabe -> aufgabe.isAufgabePassend(level, aktiv, passiv))
|
||||
.collect(Collectors.toList());
|
||||
if (!list.isEmpty()) {
|
||||
Aufgabe aufgabe = list.get(new Random().nextInt(list.size()));
|
||||
AufgabeAnzeige anzeige = new AufgabeAnzeige();
|
||||
anzeige.setNameAktiverMitspieler(aktiv.getName());
|
||||
anzeige.setAufgabeText(getAnzeigeText(aufgabe.getText(), aktiv.getName(), passiv.getName()));
|
||||
if (aufgabe.getSekundenVon() != null) {
|
||||
if (aufgabe.getSekundenBis() != null) {
|
||||
anzeige.setTimer(new Random().nextInt(aufgabe.getSekundenVon(), aufgabe.getSekundenBis()));
|
||||
} else {
|
||||
anzeige.setTimer(aufgabe.getSekundenVon());
|
||||
}
|
||||
}
|
||||
return anzeige;
|
||||
}
|
||||
}
|
||||
}
|
||||
return findeStrafe();
|
||||
}
|
||||
|
||||
private AufgabeAnzeige findeStrafe() {
|
||||
Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_AKTIV);
|
||||
if (aktiv != null) {
|
||||
Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_PASSIV, aktiv);
|
||||
if (passiv != null) {
|
||||
List<Strafe> list = aufgabenList.getStrafen().stream()
|
||||
.filter(strafe -> strafe.isAufgabePassend(level, aktiv, passiv))
|
||||
.collect(Collectors.toList());
|
||||
if (!list.isEmpty()) {
|
||||
Strafe strafe = list.get(new Random().nextInt(list.size()));
|
||||
AufgabeAnzeige anzeige = new AufgabeAnzeige();
|
||||
anzeige.setNameAktiverMitspieler(aktiv.getName());
|
||||
anzeige.setAufgabeText(getAnzeigeText(strafe.getText(), aktiv.getName(), passiv.getName()));
|
||||
if (strafe.getSekundenVon() != null) {
|
||||
if (strafe.getSekundenBis() != null) {
|
||||
anzeige.setTimer(new Random().nextInt(strafe.getSekundenVon(), strafe.getSekundenBis()));
|
||||
} else {
|
||||
anzeige.setTimer(strafe.getSekundenVon());
|
||||
}
|
||||
}
|
||||
return anzeige;
|
||||
}
|
||||
}
|
||||
}
|
||||
return findeSperre();
|
||||
}
|
||||
|
||||
private AufgabeAnzeige findeSperre() {
|
||||
Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_AKTIV);
|
||||
if (aktiv != null) {
|
||||
Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_PASSIV, aktiv);
|
||||
if (passiv != null) {
|
||||
List<Sperre> list = aufgabenList.getSperren().stream()
|
||||
.filter(sperre -> sperre.isAufgabePassend(passiv))
|
||||
.collect(Collectors.toList());
|
||||
if (!list.isEmpty()) {
|
||||
Sperre sperre = list.get(new Random().nextInt(list.size()));
|
||||
AufgabeAnzeige anzeige = new AufgabeAnzeige();
|
||||
anzeige.setNameAktiverMitspieler(aktiv.getName());
|
||||
anzeige.setAufgabeText(getAnzeigeText(sperre.getText(), aktiv.getName(), passiv.getName()));
|
||||
SperreCallback callback = new SperreCallback();
|
||||
callback.setSperreId(sperre.getSperreId());
|
||||
callback.setSpielerId(passiv.getId());
|
||||
callback.setReleaseText(getAnzeigeText(sperre.getReleaseText(), aktiv.getName(), passiv.getName()));
|
||||
anzeige.setCallback(callback);
|
||||
return anzeige;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getAnzeigeText(String textMitPlatzhaltern, String nameAktiv, String namePassiv) {
|
||||
return textMitPlatzhaltern.replace("{AKTIV}", nameAktiv).replace("{PASSIV}", namePassiv);
|
||||
}
|
||||
|
||||
private Mitspieler findeMitspielerMitRolle(RolleEnum rolle) {
|
||||
List<Mitspieler> list = mitspieler.stream()
|
||||
.filter(m -> m.getRollen().contains(rolle))
|
||||
.toList();
|
||||
return list.isEmpty() ? null : list.get(new Random().nextInt(list.size()));
|
||||
}
|
||||
|
||||
private Mitspieler findeMitspielerMitRolle(RolleEnum rolle, Mitspieler gegenspieler) {
|
||||
if (gegenspieler == null) return findeMitspielerMitRolle(rolle);
|
||||
List<Mitspieler> list = mitspieler.stream()
|
||||
.filter(m -> m != gegenspieler)
|
||||
.filter(m -> m.isPassenderSpielpartner(gegenspieler))
|
||||
.filter(m -> m.getRollen().contains(rolle))
|
||||
.toList();
|
||||
return list.isEmpty() ? null : list.get(new Random().nextInt(list.size()));
|
||||
}
|
||||
|
||||
public int getAufgabenAufAktuellemLevel() {
|
||||
return aufgabenAufAktuellemLevel;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return level;
|
||||
}
|
||||
}
|
||||
package de.oaa.xxx.games.bdsm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import de.oaa.xxx.games.bdsm.aufgaben.Aufgabe;
|
||||
import de.oaa.xxx.games.bdsm.aufgaben.AufgabenList;
|
||||
import de.oaa.xxx.games.bdsm.aufgaben.Sperre;
|
||||
import de.oaa.xxx.games.bdsm.aufgaben.Strafe;
|
||||
import de.oaa.xxx.games.bdsm.entity.BdsmGameEntity;
|
||||
import de.oaa.xxx.games.bdsm.sperre.SperreCallback;
|
||||
import de.oaa.xxx.games.bdsm.sperre.SperrenVerlaengernCallback;
|
||||
|
||||
public class BdsmGameDurchfuehren {
|
||||
|
||||
private final AufgabenList aufgabenList;
|
||||
private final List<Mitspieler> mitspieler = new ArrayList<>();
|
||||
private final List<AktiveSperre> aktiveSperren = new ArrayList<>();
|
||||
|
||||
private final Integer wahrscheinlichkeitSperre;
|
||||
private final Integer wahrscheinlichkeitStrafe;
|
||||
|
||||
private int aufgabenProLevel;
|
||||
private int level;
|
||||
private int aufgabenAufAktuellemLevel;
|
||||
|
||||
public BdsmGameDurchfuehren(BdsmGameEntity entity) throws Exception {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
aufgabenList = objectMapper.readValue(entity.getAufgaben(), AufgabenList.class);
|
||||
entity.getMitspieler().forEach(mitspielerEntity -> mitspieler.add(mitspielerEntity.toMitspieler()));
|
||||
entity.getAktiveSperren().forEach(sperreEntity -> aktiveSperren.add(sperreEntity.toSperre(mitspieler)));
|
||||
|
||||
wahrscheinlichkeitSperre = entity.getWahrscheinlichkeitSperre();
|
||||
wahrscheinlichkeitStrafe = entity.getWahrscheinlichkeitStrafe();
|
||||
|
||||
this.aufgabenProLevel = entity.getAufgabenProLevel() != null ? entity.getAufgabenProLevel() : 5;
|
||||
this.level = entity.getLevel() != null ? entity.getLevel() : 1;
|
||||
this.aufgabenAufAktuellemLevel = entity.getAufgabenAufAktuellemLevel() != null ? entity.getAufgabenAufAktuellemLevel() : 0;
|
||||
}
|
||||
|
||||
public AufgabeAnzeige getNext() {
|
||||
checkLevel();
|
||||
if (level == 6) {
|
||||
return null;
|
||||
}
|
||||
AufgabeAnzeige anzeige = null;
|
||||
int nextInt = new Random().nextInt(1, 100);
|
||||
if (nextInt == 1) {
|
||||
anzeige = findUltimativeStrafe();
|
||||
} else if (nextInt == 2) {
|
||||
anzeige = findSperreVerlaengern();
|
||||
} else if (nextInt > wahrscheinlichkeitSperre + wahrscheinlichkeitStrafe + 2) {
|
||||
anzeige = findeAufgabe();
|
||||
} else if (nextInt > wahrscheinlichkeitSperre + 2) {
|
||||
anzeige = findeStrafe();
|
||||
} else {
|
||||
anzeige = findeSperre();
|
||||
}
|
||||
if (anzeige == null) {
|
||||
Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.AUFGABE_AKTIV);
|
||||
Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.AUFGABE_PASSIV, aktiv);
|
||||
String text = "Ups, da ist etwas schief gelaufen. Keine potenzielle Aufgabe gefunden. Entweder seid ihr inzwischen so gut weggesperrt, dass wirklich keine Aufgaben mehr zur Verfügung stehen, oder uns ist ein Fehler unterlaufen. {AKTIV} und {PASSIV} überbrücken die Zeit mit ein wenig Petting.";
|
||||
anzeige = new AufgabeAnzeige();
|
||||
anzeige.setNameAktiverMitspieler(aktiv != null ? aktiv.getName() : "");
|
||||
setMitspielerInfo(anzeige, aktiv);
|
||||
anzeige.setAufgabeText(getAnzeigeText(text, aktiv != null ? aktiv.getName() : "?", passiv != null ? passiv.getName() : "?"));
|
||||
anzeige.setTimer(120);
|
||||
}
|
||||
return anzeige;
|
||||
}
|
||||
|
||||
public void backToLvl5() {
|
||||
this.level = 5;
|
||||
this.aufgabenAufAktuellemLevel = 0;
|
||||
}
|
||||
|
||||
public List<AufgabeAnzeige> getFinisher() {
|
||||
var list = new ArrayList<AufgabeAnzeige>();
|
||||
List.of(GeschlechtEnum.WEIBLICH, GeschlechtEnum.DIVERS, GeschlechtEnum.MAENNLICH).forEach(geschlecht -> {
|
||||
mitspieler.stream().filter(m -> geschlecht == m.getGeschlecht()).toList().forEach(cumming -> {
|
||||
var partner = findeMitspielerMitRolle(RolleEnum.AUFGABE_PASSIV, cumming);
|
||||
var finishers = aufgabenList.getFinisher().stream()
|
||||
.filter(finisher -> geschlecht == finisher.getGeschlecht())
|
||||
.toList();
|
||||
if (!finishers.isEmpty()) {
|
||||
var aufgabe = finishers.get(new Random().nextInt(list.size()));
|
||||
AufgabeAnzeige anzeige = new AufgabeAnzeige();
|
||||
anzeige.setNameAktiverMitspieler(cumming.getName());
|
||||
setMitspielerInfo(anzeige, cumming);
|
||||
anzeige.setAufgabeText(getAnzeigeText(aufgabe.getText(),
|
||||
cumming.getName(), partner != null ? partner.getName() : ""));
|
||||
list.add(anzeige);
|
||||
}
|
||||
});
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
private void checkLevel() {
|
||||
if (++aufgabenAufAktuellemLevel >= 1 + aufgabenProLevel) {
|
||||
aufgabenAufAktuellemLevel = 0;
|
||||
level++;
|
||||
}
|
||||
}
|
||||
|
||||
private void setMitspielerInfo(AufgabeAnzeige anzeige, Mitspieler aktiv) {
|
||||
if (aktiv != null) {
|
||||
anzeige.setMitspielerId(aktiv.getId());
|
||||
anzeige.setEigenesGeraet(aktiv.isEigenesGeraet());
|
||||
}
|
||||
}
|
||||
|
||||
private AufgabeAnzeige findUltimativeStrafe() {
|
||||
Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_AKTIV);
|
||||
if (aktiv != null) {
|
||||
Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_PASSIV, aktiv);
|
||||
if (passiv != null) {
|
||||
String text = "{AKTIV}, verschnüre {PASSIV} fachmännisch inkl. KG, Plugs, Knebel, Augenbinde und was dir sonst einfällt. Nutze die Ruhe für was auch immer du möchtest.";
|
||||
AufgabeAnzeige anzeige = new AufgabeAnzeige();
|
||||
anzeige.setNameAktiverMitspieler(aktiv.getName());
|
||||
setMitspielerInfo(anzeige, aktiv);
|
||||
anzeige.setAufgabeText(getAnzeigeText(text, aktiv.getName(), passiv.getName()));
|
||||
anzeige.setTimer(new Random().nextInt(1800, 7200));
|
||||
return anzeige;
|
||||
}
|
||||
}
|
||||
return findeStrafe();
|
||||
}
|
||||
|
||||
private AufgabeAnzeige findSperreVerlaengern() {
|
||||
if (!aktiveSperren.isEmpty()) {
|
||||
AktiveSperre sperre = aktiveSperren.get(new Random().nextInt(aktiveSperren.size()));
|
||||
Mitspieler passiv = sperre.getMitspieler();
|
||||
Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_AKTIV, passiv);
|
||||
if (aktiv != null) {
|
||||
String text = "{AKTIV}, du entscheidest. Sollen alle bestehenden Zeitstrafen von {PASSIV} verlängert werden...?";
|
||||
AufgabeAnzeige anzeige = new AufgabeAnzeige();
|
||||
anzeige.setAufgabeText(getAnzeigeText(text, aktiv.getName(), passiv.getName()));
|
||||
anzeige.setNameAktiverMitspieler(aktiv.getName());
|
||||
setMitspielerInfo(anzeige, aktiv);
|
||||
SperrenVerlaengernCallback callback = new SperrenVerlaengernCallback();
|
||||
callback.setFaktor(new Random().nextInt(2, 4));
|
||||
callback.setSpielerId(passiv.getId());
|
||||
anzeige.setCallback(callback);
|
||||
return anzeige;
|
||||
}
|
||||
}
|
||||
return findeSperre();
|
||||
}
|
||||
|
||||
private AufgabeAnzeige findeAufgabe() {
|
||||
Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.AUFGABE_AKTIV);
|
||||
if (aktiv != null) {
|
||||
Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.AUFGABE_PASSIV, aktiv);
|
||||
if (passiv != null) {
|
||||
List<Aufgabe> list = aufgabenList.getAufgaben().stream()
|
||||
.filter(aufgabe -> aufgabe.isAufgabePassend(level, aktiv, passiv))
|
||||
.collect(Collectors.toList());
|
||||
if (!list.isEmpty()) {
|
||||
Aufgabe aufgabe = list.get(new Random().nextInt(list.size()));
|
||||
AufgabeAnzeige anzeige = new AufgabeAnzeige();
|
||||
anzeige.setNameAktiverMitspieler(aktiv.getName());
|
||||
setMitspielerInfo(anzeige, aktiv);
|
||||
anzeige.setAufgabeText(getAnzeigeText(aufgabe.getText(), aktiv.getName(), passiv.getName()));
|
||||
if (aufgabe.getSekundenVon() != null) {
|
||||
if (aufgabe.getSekundenBis() != null) {
|
||||
anzeige.setTimer(new Random().nextInt(aufgabe.getSekundenVon(), aufgabe.getSekundenBis()));
|
||||
} else {
|
||||
anzeige.setTimer(aufgabe.getSekundenVon());
|
||||
}
|
||||
}
|
||||
return anzeige;
|
||||
}
|
||||
}
|
||||
}
|
||||
return findeStrafe();
|
||||
}
|
||||
|
||||
private AufgabeAnzeige findeStrafe() {
|
||||
Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_AKTIV);
|
||||
if (aktiv != null) {
|
||||
Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_PASSIV, aktiv);
|
||||
if (passiv != null) {
|
||||
List<Strafe> list = aufgabenList.getStrafen().stream()
|
||||
.filter(strafe -> strafe.isAufgabePassend(level, aktiv, passiv))
|
||||
.collect(Collectors.toList());
|
||||
if (!list.isEmpty()) {
|
||||
Strafe strafe = list.get(new Random().nextInt(list.size()));
|
||||
AufgabeAnzeige anzeige = new AufgabeAnzeige();
|
||||
anzeige.setNameAktiverMitspieler(aktiv.getName());
|
||||
setMitspielerInfo(anzeige, aktiv);
|
||||
anzeige.setAufgabeText(getAnzeigeText(strafe.getText(), aktiv.getName(), passiv.getName()));
|
||||
if (strafe.getSekundenVon() != null) {
|
||||
if (strafe.getSekundenBis() != null) {
|
||||
anzeige.setTimer(new Random().nextInt(strafe.getSekundenVon(), strafe.getSekundenBis()));
|
||||
} else {
|
||||
anzeige.setTimer(strafe.getSekundenVon());
|
||||
}
|
||||
}
|
||||
return anzeige;
|
||||
}
|
||||
}
|
||||
}
|
||||
return findeSperre();
|
||||
}
|
||||
|
||||
private AufgabeAnzeige findeSperre() {
|
||||
Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_AKTIV);
|
||||
if (aktiv != null) {
|
||||
Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_PASSIV, aktiv);
|
||||
if (passiv != null) {
|
||||
List<Sperre> list = aufgabenList.getSperren().stream()
|
||||
.filter(sperre -> sperre.isAufgabePassend(passiv))
|
||||
.collect(Collectors.toList());
|
||||
if (!list.isEmpty()) {
|
||||
Sperre sperre = list.get(new Random().nextInt(list.size()));
|
||||
AufgabeAnzeige anzeige = new AufgabeAnzeige();
|
||||
anzeige.setNameAktiverMitspieler(aktiv.getName());
|
||||
setMitspielerInfo(anzeige, aktiv);
|
||||
anzeige.setAufgabeText(getAnzeigeText(sperre.getText(), aktiv.getName(), passiv.getName()));
|
||||
SperreCallback callback = new SperreCallback();
|
||||
callback.setSperreId(sperre.getSperreId());
|
||||
callback.setSpielerId(passiv.getId());
|
||||
callback.setReleaseText(getAnzeigeText(sperre.getReleaseText(), aktiv.getName(), passiv.getName()));
|
||||
anzeige.setCallback(callback);
|
||||
return anzeige;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getAnzeigeText(String textMitPlatzhaltern, String nameAktiv, String namePassiv) {
|
||||
return textMitPlatzhaltern.replace("{AKTIV}", nameAktiv).replace("{PASSIV}", namePassiv);
|
||||
}
|
||||
|
||||
private Mitspieler findeMitspielerMitRolle(RolleEnum rolle) {
|
||||
List<Mitspieler> list = mitspieler.stream()
|
||||
.filter(m -> m.getRollen().contains(rolle))
|
||||
.toList();
|
||||
return list.isEmpty() ? null : list.get(new Random().nextInt(list.size()));
|
||||
}
|
||||
|
||||
private Mitspieler findeMitspielerMitRolle(RolleEnum rolle, Mitspieler gegenspieler) {
|
||||
if (gegenspieler == null) return findeMitspielerMitRolle(rolle);
|
||||
List<Mitspieler> list = mitspieler.stream()
|
||||
.filter(m -> m != gegenspieler)
|
||||
.filter(m -> m.isPassenderSpielpartner(gegenspieler))
|
||||
.filter(m -> m.getRollen().contains(rolle))
|
||||
.toList();
|
||||
return list.isEmpty() ? null : list.get(new Random().nextInt(list.size()));
|
||||
}
|
||||
|
||||
public int getAufgabenAufAktuellemLevel() {
|
||||
return aufgabenAufAktuellemLevel;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return level;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.oaa.xxx.session;
|
||||
package de.oaa.xxx.games.bdsm;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.oaa.xxx.session;
|
||||
package de.oaa.xxx.games.bdsm;
|
||||
|
||||
public enum GeschlechtEnum {
|
||||
WEIBLICH,
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.oaa.xxx.session;
|
||||
package de.oaa.xxx.games.bdsm;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
@@ -11,6 +11,8 @@ import java.util.UUID;
|
||||
public class Mitspieler {
|
||||
|
||||
private UUID id;
|
||||
private UUID userId;
|
||||
private boolean eigenesGeraet;
|
||||
private String name;
|
||||
private GeschlechtEnum geschlecht;
|
||||
private List<GeschlechtEnum> spieltMit;
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.oaa.xxx.session;
|
||||
package de.oaa.xxx.games.bdsm;
|
||||
|
||||
public enum RolleEnum {
|
||||
BESTRAFUNG_AKTIV,
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.oaa.xxx.session;
|
||||
package de.oaa.xxx.games.bdsm;
|
||||
|
||||
public enum Werkzeug {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package de.oaa.xxx.session.aufgaben;
|
||||
package de.oaa.xxx.games.bdsm.aufgaben;
|
||||
|
||||
import de.oaa.xxx.session.Mitspieler;
|
||||
import de.oaa.xxx.session.Werkzeug;
|
||||
import de.oaa.xxx.games.bdsm.Mitspieler;
|
||||
import de.oaa.xxx.games.bdsm.Werkzeug;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.oaa.xxx.session.aufgaben;
|
||||
package de.oaa.xxx.games.bdsm.aufgaben;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
@@ -1,7 +1,7 @@
|
||||
package de.oaa.xxx.session.aufgaben;
|
||||
package de.oaa.xxx.games.bdsm.aufgaben;
|
||||
|
||||
import de.oaa.xxx.session.GeschlechtEnum;
|
||||
import de.oaa.xxx.session.Werkzeug;
|
||||
import de.oaa.xxx.games.bdsm.GeschlechtEnum;
|
||||
import de.oaa.xxx.games.bdsm.Werkzeug;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package de.oaa.xxx.session.aufgaben;
|
||||
package de.oaa.xxx.games.bdsm.aufgaben;
|
||||
|
||||
import de.oaa.xxx.session.Mitspieler;
|
||||
import de.oaa.xxx.session.Werkzeug;
|
||||
import de.oaa.xxx.games.bdsm.Mitspieler;
|
||||
import de.oaa.xxx.games.bdsm.Werkzeug;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package de.oaa.xxx.session.aufgaben;
|
||||
package de.oaa.xxx.games.bdsm.aufgaben;
|
||||
|
||||
import de.oaa.xxx.session.Mitspieler;
|
||||
import de.oaa.xxx.session.Werkzeug;
|
||||
import de.oaa.xxx.games.bdsm.Mitspieler;
|
||||
import de.oaa.xxx.games.bdsm.Werkzeug;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
package de.oaa.xxx.games.bdsm.controller;
|
||||
|
||||
import de.oaa.xxx.games.bdsm.entity.BdsmEinladungEntity;
|
||||
import de.oaa.xxx.games.bdsm.entity.BdsmEinladungEntity.Status;
|
||||
import de.oaa.xxx.games.bdsm.repository.BdsmEinladungRepository;
|
||||
import de.oaa.xxx.social.SystemMessageService;
|
||||
import de.oaa.xxx.social.entity.MessageCause;
|
||||
import de.oaa.xxx.social.repository.FriendshipRepository;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/bdsm/einladung")
|
||||
@Transactional
|
||||
public class BdsmEinladungController {
|
||||
|
||||
private final BdsmEinladungRepository einladungRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final FriendshipRepository friendshipRepository;
|
||||
private final SystemMessageService systemMessageService;
|
||||
|
||||
public BdsmEinladungController(BdsmEinladungRepository einladungRepository,
|
||||
UserRepository userRepository,
|
||||
FriendshipRepository friendshipRepository,
|
||||
SystemMessageService systemMessageService) {
|
||||
this.einladungRepository = einladungRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.friendshipRepository = friendshipRepository;
|
||||
this.systemMessageService = systemMessageService;
|
||||
}
|
||||
|
||||
record EinladungRequest(UUID setupId, int slotIndex, UUID inviteeId) {}
|
||||
record AntwortRequest(boolean accepted, String mode) {} // mode: OWN_DEVICE | HOST_DEVICE
|
||||
|
||||
private UUID currentUserId(Principal principal) {
|
||||
return userRepository.findByEmail(principal.getName())
|
||||
.map(u -> u.getUserId()).orElse(null);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<Map<String, Object>> sendEinladung(@RequestBody EinladungRequest req, Principal principal) {
|
||||
UUID inviterId = currentUserId(principal);
|
||||
if (inviterId == null) return ResponseEntity.status(401).build();
|
||||
|
||||
// Freundschaft prüfen
|
||||
var friendship = friendshipRepository.findExisting(inviterId, req.inviteeId());
|
||||
if (friendship.isEmpty() || friendship.get().getStatus() != de.oaa.xxx.social.entity.FriendshipEntity.Status.ACCEPTED) {
|
||||
return ResponseEntity.status(403).build();
|
||||
}
|
||||
|
||||
// Alte Einladung für diesen Slot canceln
|
||||
einladungRepository.findBySetupId(req.setupId()).stream()
|
||||
.filter(e -> e.getSlotIndex() == req.slotIndex() && e.getStatus() == Status.PENDING)
|
||||
.forEach(e -> e.setStatus(Status.CANCELLED));
|
||||
|
||||
BdsmEinladungEntity entity = new BdsmEinladungEntity();
|
||||
entity.setEinladungId(UUID.randomUUID());
|
||||
entity.setSetupId(req.setupId());
|
||||
entity.setInviterId(inviterId);
|
||||
entity.setInviteeId(req.inviteeId());
|
||||
entity.setSlotIndex(req.slotIndex());
|
||||
entity.setStatus(Status.PENDING);
|
||||
entity.setCreatedAt(LocalDateTime.now());
|
||||
einladungRepository.save(entity);
|
||||
|
||||
String inviterName = userRepository.findById(inviterId).map(u -> u.getName()).orElse("Jemand");
|
||||
systemMessageService.send(
|
||||
inviterId, req.inviteeId(),
|
||||
inviterName + " hat dich zum BDSM Game eingeladen.",
|
||||
"/einladungen.html",
|
||||
MessageCause.INVITATION
|
||||
);
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("einladungId", entity.getEinladungId());
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> cancelEinladung(@PathVariable UUID id, Principal principal) {
|
||||
UUID userId = currentUserId(principal);
|
||||
if (userId == null) return ResponseEntity.status(401).build();
|
||||
BdsmEinladungEntity e = einladungRepository.findById(id).orElse(null);
|
||||
if (e == null) return ResponseEntity.notFound().build();
|
||||
if (!e.getInviterId().equals(userId)) return ResponseEntity.status(403).build();
|
||||
e.setStatus(Status.CANCELLED);
|
||||
return ResponseEntity.accepted().build();
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<Map<String, Object>>> getBySetupId(@RequestParam UUID setupId, Principal principal) {
|
||||
UUID userId = currentUserId(principal);
|
||||
if (userId == null) return ResponseEntity.status(401).build();
|
||||
List<Map<String, Object>> list = einladungRepository.findBySetupId(setupId).stream()
|
||||
.map(this::toMap).toList();
|
||||
return ResponseEntity.ok(list);
|
||||
}
|
||||
|
||||
@GetMapping("/pending")
|
||||
public ResponseEntity<List<Map<String, Object>>> getPending(Principal principal) {
|
||||
UUID userId = currentUserId(principal);
|
||||
if (userId == null) return ResponseEntity.status(401).build();
|
||||
List<Map<String, Object>> list = einladungRepository.findByInviteeIdAndStatus(userId, Status.PENDING)
|
||||
.stream().map(e -> {
|
||||
Map<String, Object> m = toMap(e);
|
||||
userRepository.findById(e.getInviterId()).ifPresent(u -> {
|
||||
m.put("inviterName", u.getName());
|
||||
m.put("inviterAvatar", u.getProfilePicture() != null ? u.getProfilePicture() : "");
|
||||
});
|
||||
return m;
|
||||
}).toList();
|
||||
return ResponseEntity.ok(list);
|
||||
}
|
||||
|
||||
@GetMapping("/sent")
|
||||
public ResponseEntity<List<Map<String, Object>>> getSent(Principal principal) {
|
||||
UUID userId = currentUserId(principal);
|
||||
if (userId == null) return ResponseEntity.status(401).build();
|
||||
List<Map<String, Object>> list = einladungRepository.findByInviterIdAndStatus(userId, Status.PENDING)
|
||||
.stream().map(e -> {
|
||||
Map<String, Object> m = toMap(e);
|
||||
userRepository.findById(e.getInviteeId()).ifPresent(u -> {
|
||||
m.put("inviteeName", u.getName());
|
||||
m.put("inviteeAvatar", u.getProfilePicture() != null ? u.getProfilePicture() : "");
|
||||
});
|
||||
return m;
|
||||
}).toList();
|
||||
return ResponseEntity.ok(list);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<Map<String, Object>> getById(@PathVariable UUID id, Principal principal) {
|
||||
UUID userId = currentUserId(principal);
|
||||
if (userId == null) return ResponseEntity.status(401).build();
|
||||
BdsmEinladungEntity e = einladungRepository.findById(id).orElse(null);
|
||||
if (e == null) return ResponseEntity.notFound().build();
|
||||
if (!e.getInviteeId().equals(userId) && !e.getInviterId().equals(userId)) {
|
||||
return ResponseEntity.status(403).build();
|
||||
}
|
||||
Map<String, Object> m = toMap(e);
|
||||
userRepository.findById(e.getInviterId()).ifPresent(u -> {
|
||||
m.put("inviterName", u.getName());
|
||||
m.put("inviterAvatar", u.getProfilePicture() != null ? u.getProfilePicture() : "");
|
||||
});
|
||||
return ResponseEntity.ok(m);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/antwort")
|
||||
public ResponseEntity<Void> antwort(@PathVariable UUID id, @RequestBody AntwortRequest req, Principal principal) {
|
||||
UUID userId = currentUserId(principal);
|
||||
if (userId == null) return ResponseEntity.status(401).build();
|
||||
BdsmEinladungEntity e = einladungRepository.findById(id).orElse(null);
|
||||
if (e == null) return ResponseEntity.notFound().build();
|
||||
if (!e.getInviteeId().equals(userId)) return ResponseEntity.status(403).build();
|
||||
if (e.getStatus() != Status.PENDING) return ResponseEntity.badRequest().build();
|
||||
if (!req.accepted()) {
|
||||
e.setStatus(Status.DECLINED);
|
||||
} else if ("OWN_DEVICE".equals(req.mode())) {
|
||||
e.setStatus(Status.ACCEPTED_OWN);
|
||||
} else {
|
||||
e.setStatus(Status.ACCEPTED_HOST);
|
||||
}
|
||||
return ResponseEntity.accepted().build();
|
||||
}
|
||||
|
||||
private Map<String, Object> toMap(BdsmEinladungEntity e) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("einladungId", e.getEinladungId());
|
||||
m.put("setupId", e.getSetupId());
|
||||
m.put("slotIndex", e.getSlotIndex());
|
||||
m.put("inviteeId", e.getInviteeId());
|
||||
m.put("inviterId", e.getInviterId());
|
||||
m.put("status", e.getStatus().name());
|
||||
m.put("sessionId", e.getSessionId());
|
||||
return m;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,22 @@
|
||||
package de.oaa.xxx.session.controller;
|
||||
package de.oaa.xxx.games.bdsm.controller;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import de.oaa.xxx.session.AufgabeAnzeige;
|
||||
import de.oaa.xxx.session.Mitspieler;
|
||||
import de.oaa.xxx.session.Session;
|
||||
import de.oaa.xxx.session.SessionDurchfuehren;
|
||||
import de.oaa.xxx.session.aufgaben.AufgabenList;
|
||||
import de.oaa.xxx.session.entity.MitspielerEntity;
|
||||
import de.oaa.xxx.session.entity.SessionEntity;
|
||||
import de.oaa.xxx.session.repository.AktiveSperreRepository;
|
||||
import de.oaa.xxx.session.repository.MitspielerRepository;
|
||||
import de.oaa.xxx.session.repository.SessionRepository;
|
||||
import de.oaa.xxx.games.bdsm.AufgabeAnzeige;
|
||||
import de.oaa.xxx.games.bdsm.Mitspieler;
|
||||
import de.oaa.xxx.games.bdsm.BdsmGame;
|
||||
import de.oaa.xxx.games.bdsm.BdsmGameDurchfuehren;
|
||||
import de.oaa.xxx.games.bdsm.aufgaben.AufgabenList;
|
||||
import de.oaa.xxx.games.bdsm.entity.MitspielerEntity;
|
||||
import de.oaa.xxx.games.bdsm.entity.BdsmGameEntity;
|
||||
import de.oaa.xxx.games.bdsm.entity.BdsmEinladungEntity;
|
||||
import de.oaa.xxx.games.bdsm.repository.AktiveSperreRepository;
|
||||
import de.oaa.xxx.games.bdsm.repository.BdsmEinladungRepository;
|
||||
import de.oaa.xxx.games.bdsm.repository.MitspielerRepository;
|
||||
import de.oaa.xxx.games.bdsm.repository.BdsmGameRepository;
|
||||
import de.oaa.xxx.games.history.GameHistoryEntity;
|
||||
import de.oaa.xxx.games.history.GameHistoryRepository;
|
||||
import de.oaa.xxx.games.history.GameRole;
|
||||
import de.oaa.xxx.games.history.GameType;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -21,59 +27,69 @@ import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/session")
|
||||
@RequestMapping("/bdsm")
|
||||
@Transactional
|
||||
public class SessionController {
|
||||
public class BdsmGameController {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SessionController.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(BdsmGameController.class);
|
||||
|
||||
private final SessionRepository sessionRepository;
|
||||
private final BdsmGameRepository sessionRepository;
|
||||
private final MitspielerRepository mitspielerRepository;
|
||||
private final AktiveSperreRepository aktiveSperreRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final GameHistoryRepository gameHistoryRepository;
|
||||
private final BdsmEinladungRepository einladungRepository;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public SessionController(SessionRepository sessionRepository, MitspielerRepository mitspielerRepository,
|
||||
public BdsmGameController(BdsmGameRepository sessionRepository, MitspielerRepository mitspielerRepository,
|
||||
AktiveSperreRepository aktiveSperreRepository, UserRepository userRepository,
|
||||
GameHistoryRepository gameHistoryRepository, BdsmEinladungRepository einladungRepository,
|
||||
ObjectMapper objectMapper) {
|
||||
this.sessionRepository = sessionRepository;
|
||||
this.mitspielerRepository = mitspielerRepository;
|
||||
this.aktiveSperreRepository = aktiveSperreRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.gameHistoryRepository = gameHistoryRepository;
|
||||
this.einladungRepository = einladungRepository;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@GetMapping("/{sessionId}")
|
||||
public ResponseEntity<Session> getBySessionId(@PathVariable UUID sessionId) {
|
||||
public ResponseEntity<BdsmGame> getBySessionId(@PathVariable UUID sessionId) {
|
||||
return sessionRepository.findById(sessionId)
|
||||
.map(entity -> ResponseEntity.ok(toSession(entity)))
|
||||
.orElse(ResponseEntity.noContent().build());
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<Session> getByUserId(@RequestParam UUID userId) {
|
||||
public ResponseEntity<BdsmGame> getByUserId(@RequestParam UUID userId) {
|
||||
return sessionRepository.findByUserId(userId)
|
||||
.map(entity -> ResponseEntity.ok(toSession(entity)))
|
||||
.orElse(ResponseEntity.noContent().build());
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<Void> create(@RequestBody Session session) {
|
||||
public ResponseEntity<Void> create(@RequestBody BdsmGame session) {
|
||||
String email = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
UUID userId = userRepository.findByEmail(email).map(u -> u.getUserId()).orElse(null);
|
||||
if (userId == null) return ResponseEntity.status(401).build();
|
||||
SessionEntity entity = new SessionEntity();
|
||||
BdsmGameEntity entity = new BdsmGameEntity();
|
||||
entity.setSessionId(UUID.randomUUID());
|
||||
entity.setUserId(userId);
|
||||
entity.setAufgabenAufAktuellemLevel(0);
|
||||
@@ -85,8 +101,16 @@ public class SessionController {
|
||||
entity.setWahrscheinlichkeitStrafe(session.getWahrscheinlichkeitStrafe() != null ? session.getWahrscheinlichkeitStrafe() : 10);
|
||||
entity.setZeitfaktorZeitstrafen(session.getZeitfaktorZeitstrafen() != null ? session.getZeitfaktorZeitstrafen() : 1.0);
|
||||
entity.setLevel(1);
|
||||
entity.setSetupId(session.getSetupId());
|
||||
sessionRepository.save(entity);
|
||||
LOGGER.debug("Session gestartet [sessionId={}, userId={}, aufgabenProLevel={}, wahrscheinlichkeitStrafe={}%, wahrscheinlichkeitSperre={}%, zeitfaktorZeitstrafen={}]",
|
||||
// Akzeptierte Einladungen mit der neuen Session verknüpfen
|
||||
if (session.getSetupId() != null) {
|
||||
einladungRepository.findBySetupId(session.getSetupId()).stream()
|
||||
.filter(e -> e.getStatus() == BdsmEinladungEntity.Status.ACCEPTED_OWN
|
||||
|| e.getStatus() == BdsmEinladungEntity.Status.ACCEPTED_HOST)
|
||||
.forEach(e -> e.setSessionId(entity.getSessionId()));
|
||||
}
|
||||
LOGGER.debug("BdsmGame gestartet [sessionId={}, userId={}, aufgabenProLevel={}, wahrscheinlichkeitStrafe={}%, wahrscheinlichkeitSperre={}%, zeitfaktorZeitstrafen={}]",
|
||||
entity.getSessionId(), entity.getUserId(), entity.getAufgabenProLevel(),
|
||||
entity.getWahrscheinlichkeitStrafe(), entity.getWahrscheinlichkeitSperre(),
|
||||
entity.getZeitfaktorZeitstrafen());
|
||||
@@ -96,9 +120,20 @@ public class SessionController {
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
public ResponseEntity<Void> deleteSession(@RequestBody Session session) {
|
||||
public ResponseEntity<Void> deleteSession(@RequestBody BdsmGame session) {
|
||||
return sessionRepository.findById(session.getSessionId())
|
||||
.map(entity -> {
|
||||
LocalDateTime endTime = LocalDateTime.now();
|
||||
long durationMinutes = Duration.between(entity.getStartZeit(), endTime).toMinutes();
|
||||
|
||||
GameHistoryEntity entry = new GameHistoryEntity();
|
||||
entry.setGameType(GameType.BDSM);
|
||||
entry.setStartTime(entity.getStartZeit());
|
||||
entry.setEndTime(endTime);
|
||||
entry.setDurationMinutes(durationMinutes);
|
||||
entry.addParticipant(entity.getUserId(), GameRole.PLAYER);
|
||||
gameHistoryRepository.save(entry);
|
||||
|
||||
aktiveSperreRepository.deleteAll(entity.getAktiveSperren());
|
||||
mitspielerRepository.deleteAll(entity.getMitspieler());
|
||||
sessionRepository.delete(entity);
|
||||
@@ -114,7 +149,7 @@ public class SessionController {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
String aufgaben = objectMapper.writeValueAsString(list);
|
||||
SessionEntity session = sessionRepository.findById(sessionId).orElse(null);
|
||||
BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null);
|
||||
if (session == null) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
@@ -130,12 +165,12 @@ public class SessionController {
|
||||
@GetMapping("/{sessionId}/aufgaben/next")
|
||||
public ResponseEntity<AufgabeAnzeige> getNextAufgabe(@PathVariable UUID sessionId) {
|
||||
try {
|
||||
SessionEntity session = sessionRepository.findById(sessionId).orElse(null);
|
||||
BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null);
|
||||
if (session == null) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
session.setLetzteAktivitaet(LocalDateTime.now());
|
||||
SessionDurchfuehren durchfuehren = new SessionDurchfuehren(session);
|
||||
BdsmGameDurchfuehren durchfuehren = new BdsmGameDurchfuehren(session);
|
||||
AufgabeAnzeige next = durchfuehren.getNext();
|
||||
session.setLevel(durchfuehren.getLevel());
|
||||
session.setAufgabenAufAktuellemLevel(durchfuehren.getAufgabenAufAktuellemLevel());
|
||||
@@ -165,7 +200,7 @@ public class SessionController {
|
||||
|| mitspieler.getVerfuegbareWerkzeuge() == null || mitspieler.getVerfuegbareWerkzeuge().isEmpty()) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
SessionEntity session = sessionRepository.findById(sessionId).orElse(null);
|
||||
BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null);
|
||||
if (session == null) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
@@ -176,6 +211,8 @@ public class SessionController {
|
||||
entity.setRollen(mitspieler.getRollen());
|
||||
entity.setSpieltMit(mitspieler.getSpieltMit());
|
||||
entity.setWerkzeuge(mitspieler.getVerfuegbareWerkzeuge());
|
||||
entity.setUserId(mitspieler.getUserId());
|
||||
entity.setEigenesGeraet(mitspieler.isEigenesGeraet());
|
||||
entity.setSession(session);
|
||||
mitspielerRepository.save(entity);
|
||||
return ResponseEntity.accepted().build();
|
||||
@@ -184,9 +221,9 @@ public class SessionController {
|
||||
@GetMapping("/{sessionId}/finisher")
|
||||
public ResponseEntity<List<AufgabeAnzeige>> getFinisher(@PathVariable UUID sessionId) {
|
||||
try {
|
||||
SessionEntity session = sessionRepository.findById(sessionId).orElse(null);
|
||||
BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null);
|
||||
if (session == null) return ResponseEntity.badRequest().build();
|
||||
SessionDurchfuehren durchfuehren = new SessionDurchfuehren(session);
|
||||
BdsmGameDurchfuehren durchfuehren = new BdsmGameDurchfuehren(session);
|
||||
return ResponseEntity.ok(durchfuehren.getFinisher());
|
||||
} catch (Exception exception) {
|
||||
LOGGER.error(exception.getMessage(), exception);
|
||||
@@ -197,9 +234,9 @@ public class SessionController {
|
||||
@PostMapping("/{sessionId}/backToLevel5")
|
||||
public ResponseEntity<Void> backToLevel5(@PathVariable UUID sessionId) {
|
||||
try {
|
||||
SessionEntity session = sessionRepository.findById(sessionId).orElse(null);
|
||||
BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null);
|
||||
if (session == null) return ResponseEntity.badRequest().build();
|
||||
SessionDurchfuehren durchfuehren = new SessionDurchfuehren(session);
|
||||
BdsmGameDurchfuehren durchfuehren = new BdsmGameDurchfuehren(session);
|
||||
durchfuehren.backToLvl5();
|
||||
session.setLevel(durchfuehren.getLevel());
|
||||
session.setAufgabenAufAktuellemLevel(durchfuehren.getAufgabenAufAktuellemLevel());
|
||||
@@ -211,8 +248,62 @@ public class SessionController {
|
||||
}
|
||||
}
|
||||
|
||||
private Session toSession(SessionEntity entity) {
|
||||
Session session = new Session();
|
||||
@GetMapping("/{sessionId}/mitspieler/me")
|
||||
public ResponseEntity<Map<String, Object>> getMeinMitspieler(@PathVariable UUID sessionId, Principal principal) {
|
||||
UUID userId = userRepository.findByEmail(principal.getName()).map(u -> u.getUserId()).orElse(null);
|
||||
if (userId == null) return ResponseEntity.status(401).build();
|
||||
BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null);
|
||||
if (session == null) return ResponseEntity.notFound().build();
|
||||
return session.getMitspieler().stream()
|
||||
.filter(m -> userId.equals(m.getUserId()))
|
||||
.findFirst()
|
||||
.map(m -> {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("mitspielerId", m.getMitspielerId());
|
||||
result.put("name", m.getName());
|
||||
result.put("eigenesGeraet", m.isEigenesGeraet());
|
||||
return ResponseEntity.ok(result);
|
||||
})
|
||||
.orElse(ResponseEntity.noContent().build());
|
||||
}
|
||||
|
||||
record ActiveTaskRequest(String taskJson, LocalDateTime timerStartedAt) {}
|
||||
record ActiveTaskResponse(String taskJson, Long elapsedSeconds) {}
|
||||
|
||||
@PutMapping("/{sessionId}/active-task")
|
||||
public ResponseEntity<Void> setActiveTask(@PathVariable UUID sessionId, @RequestBody ActiveTaskRequest req) {
|
||||
BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null);
|
||||
if (session == null) return ResponseEntity.notFound().build();
|
||||
session.setActiveTaskJson(req.taskJson());
|
||||
session.setTaskStartedAt(req.timerStartedAt());
|
||||
sessionRepository.save(session);
|
||||
return ResponseEntity.accepted().build();
|
||||
}
|
||||
|
||||
@DeleteMapping("/{sessionId}/active-task")
|
||||
public ResponseEntity<Void> clearActiveTask(@PathVariable UUID sessionId) {
|
||||
BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null);
|
||||
if (session == null) return ResponseEntity.notFound().build();
|
||||
session.setActiveTaskJson(null);
|
||||
session.setTaskStartedAt(null);
|
||||
sessionRepository.save(session);
|
||||
return ResponseEntity.accepted().build();
|
||||
}
|
||||
|
||||
@GetMapping("/{sessionId}/active-task")
|
||||
public ResponseEntity<ActiveTaskResponse> getActiveTask(@PathVariable UUID sessionId) {
|
||||
BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null);
|
||||
if (session == null) return ResponseEntity.notFound().build();
|
||||
if (session.getActiveTaskJson() == null) return ResponseEntity.noContent().build();
|
||||
Long elapsed = null;
|
||||
if (session.getTaskStartedAt() != null) {
|
||||
elapsed = Duration.between(session.getTaskStartedAt(), LocalDateTime.now()).getSeconds();
|
||||
}
|
||||
return ResponseEntity.ok(new ActiveTaskResponse(session.getActiveTaskJson(), elapsed));
|
||||
}
|
||||
|
||||
private BdsmGame toSession(BdsmGameEntity entity) {
|
||||
BdsmGame session = new BdsmGame();
|
||||
session.setSessionId(entity.getSessionId());
|
||||
session.setUserId(entity.getUserId());
|
||||
session.setAufgabenProLevel(entity.getAufgabenProLevel());
|
||||
@@ -1,15 +1,15 @@
|
||||
package de.oaa.xxx.session.controller;
|
||||
package de.oaa.xxx.games.bdsm.controller;
|
||||
|
||||
import de.oaa.xxx.session.AktiveSperre;
|
||||
import de.oaa.xxx.session.Mitspieler;
|
||||
import de.oaa.xxx.session.entity.AktiveSperreEntity;
|
||||
import de.oaa.xxx.session.entity.SessionEntity;
|
||||
import de.oaa.xxx.session.repository.AktiveSperreRepository;
|
||||
import de.oaa.xxx.session.repository.MitspielerRepository;
|
||||
import de.oaa.xxx.session.repository.SessionRepository;
|
||||
import de.oaa.xxx.session.sperre.SperreCallback;
|
||||
import de.oaa.xxx.session.sperre.SperreVerarbeiten;
|
||||
import de.oaa.xxx.session.sperre.SperrenVerlaengernCallback;
|
||||
import de.oaa.xxx.games.bdsm.AktiveSperre;
|
||||
import de.oaa.xxx.games.bdsm.Mitspieler;
|
||||
import de.oaa.xxx.games.bdsm.entity.AktiveSperreEntity;
|
||||
import de.oaa.xxx.games.bdsm.entity.BdsmGameEntity;
|
||||
import de.oaa.xxx.games.bdsm.repository.AktiveSperreRepository;
|
||||
import de.oaa.xxx.games.bdsm.repository.MitspielerRepository;
|
||||
import de.oaa.xxx.games.bdsm.repository.BdsmGameRepository;
|
||||
import de.oaa.xxx.games.bdsm.sperre.SperreCallback;
|
||||
import de.oaa.xxx.games.bdsm.sperre.SperreVerarbeiten;
|
||||
import de.oaa.xxx.games.bdsm.sperre.SperrenVerlaengernCallback;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -26,18 +26,18 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController("sessionSperreController")
|
||||
@RequestMapping("/session/sperre")
|
||||
@RestController("bdsmSperreController")
|
||||
@RequestMapping("/bdsm/sperre")
|
||||
@Transactional
|
||||
public class SperreController {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SperreController.class);
|
||||
|
||||
private final SessionRepository sessionRepository;
|
||||
private final BdsmGameRepository sessionRepository;
|
||||
private final MitspielerRepository mitspielerRepository;
|
||||
private final AktiveSperreRepository aktiveSperreRepository;
|
||||
|
||||
public SperreController(SessionRepository sessionRepository, MitspielerRepository mitspielerRepository,
|
||||
public SperreController(BdsmGameRepository sessionRepository, MitspielerRepository mitspielerRepository,
|
||||
AktiveSperreRepository aktiveSperreRepository) {
|
||||
this.sessionRepository = sessionRepository;
|
||||
this.mitspielerRepository = mitspielerRepository;
|
||||
@@ -78,7 +78,7 @@ public class SperreController {
|
||||
@GetMapping("/aktive")
|
||||
public ResponseEntity<List<AktiveSperre>> getAktiveSperren(@RequestParam UUID sessionId) {
|
||||
try {
|
||||
SessionEntity session = sessionRepository.findById(sessionId).orElse(null);
|
||||
BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null);
|
||||
if (session == null) return ResponseEntity.noContent().build();
|
||||
List<Mitspieler> mitspielerList = session.getMitspieler().stream()
|
||||
.map(m -> m.toMitspieler())
|
||||
@@ -1,8 +1,8 @@
|
||||
package de.oaa.xxx.session.entity;
|
||||
package de.oaa.xxx.games.bdsm.entity;
|
||||
|
||||
import de.oaa.xxx.session.AktiveSperre;
|
||||
import de.oaa.xxx.session.Mitspieler;
|
||||
import de.oaa.xxx.session.Werkzeug;
|
||||
import de.oaa.xxx.games.bdsm.AktiveSperre;
|
||||
import de.oaa.xxx.games.bdsm.Mitspieler;
|
||||
import de.oaa.xxx.games.bdsm.Werkzeug;
|
||||
import jakarta.persistence.CollectionTable;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
@@ -50,7 +50,7 @@ public class AktiveSperreEntity {
|
||||
private String releaseText;
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "sessionId", nullable = false)
|
||||
private SessionEntity session;
|
||||
private BdsmGameEntity session;
|
||||
|
||||
public AktiveSperre toSperre(List<Mitspieler> mitspielerList) {
|
||||
AktiveSperre sperre = new AktiveSperre();
|
||||
@@ -0,0 +1,30 @@
|
||||
package de.oaa.xxx.games.bdsm.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "bdsm_defaults")
|
||||
public class BdsmDefaultsEntity {
|
||||
|
||||
@Id
|
||||
@Column(name = "user_id")
|
||||
private UUID userId;
|
||||
|
||||
@Column(length = 100)
|
||||
private String spieltMit;
|
||||
|
||||
@Column(length = 200)
|
||||
private String rollen;
|
||||
|
||||
@Column(length = 200)
|
||||
private String werkzeuge;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package de.oaa.xxx.games.bdsm.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "bdsm_einladung")
|
||||
public class BdsmEinladungEntity {
|
||||
|
||||
public enum Status {
|
||||
PENDING, ACCEPTED_OWN, ACCEPTED_HOST, DECLINED, CANCELLED
|
||||
}
|
||||
|
||||
@Id
|
||||
@Column
|
||||
private UUID einladungId;
|
||||
|
||||
@Column(nullable = false)
|
||||
private UUID setupId;
|
||||
|
||||
@Column
|
||||
private UUID sessionId;
|
||||
|
||||
@Column(nullable = false)
|
||||
private UUID inviterId;
|
||||
|
||||
@Column(nullable = false)
|
||||
private UUID inviteeId;
|
||||
|
||||
@Column(nullable = false)
|
||||
private int slotIndex;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false, length = 20)
|
||||
private Status status;
|
||||
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.oaa.xxx.session.entity;
|
||||
package de.oaa.xxx.games.bdsm.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
@@ -18,7 +18,7 @@ import java.util.UUID;
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "session")
|
||||
public class SessionEntity {
|
||||
public class BdsmGameEntity {
|
||||
|
||||
@Id
|
||||
@Column
|
||||
@@ -47,10 +47,16 @@ public class SessionEntity {
|
||||
private String aufgaben;
|
||||
@Column
|
||||
private Double zeitfaktorZeitstrafen;
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String activeTaskJson;
|
||||
@Column
|
||||
private LocalDateTime taskStartedAt;
|
||||
@Column
|
||||
private UUID setupId;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SessionEntity[sessionId=" + sessionId + ", userId=" + userId
|
||||
return "BdsmGameEntity[sessionId=" + sessionId + ", userId=" + userId
|
||||
+ ", level=" + level + ", aufgaben=" + aufgabenAufAktuellemLevel + "/" + aufgabenProLevel
|
||||
+ ", pStrafe=" + wahrscheinlichkeitStrafe + "%, pSperre=" + wahrscheinlichkeitSperre + "%"
|
||||
+ ", zeitfaktor=" + zeitfaktorZeitstrafen + ", start=" + startZeit + "]";
|
||||
@@ -1,77 +1,83 @@
|
||||
package de.oaa.xxx.session.entity;
|
||||
|
||||
import de.oaa.xxx.session.GeschlechtEnum;
|
||||
import de.oaa.xxx.session.Mitspieler;
|
||||
import de.oaa.xxx.session.RolleEnum;
|
||||
import de.oaa.xxx.session.Werkzeug;
|
||||
import jakarta.persistence.CollectionTable;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "mitspieler")
|
||||
public class MitspielerEntity {
|
||||
|
||||
@Id
|
||||
@Column
|
||||
private UUID mitspielerId;
|
||||
@Column
|
||||
private String name;
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column
|
||||
private GeschlechtEnum geschlecht;
|
||||
@Enumerated(EnumType.STRING)
|
||||
@ElementCollection(targetClass = Werkzeug.class, fetch = FetchType.EAGER)
|
||||
@CollectionTable(name = "mitspieler_werkzeuge", joinColumns = @JoinColumn(name = "mitspielerId"))
|
||||
@Column(name = "werkzeug")
|
||||
private List<Werkzeug> werkzeuge = new ArrayList<>();
|
||||
@Enumerated(EnumType.STRING)
|
||||
@ElementCollection(targetClass = GeschlechtEnum.class, fetch = FetchType.EAGER)
|
||||
@CollectionTable(name = "mitspieler_spieltMit", joinColumns = @JoinColumn(name = "mitspielerId"))
|
||||
@Column(name = "geschlecht")
|
||||
private List<GeschlechtEnum> spieltMit = new ArrayList<>();
|
||||
@Enumerated(EnumType.STRING)
|
||||
@ElementCollection(targetClass = RolleEnum.class, fetch = FetchType.EAGER)
|
||||
@CollectionTable(name = "mitspieler_rollen", joinColumns = @JoinColumn(name = "mitspielerId"))
|
||||
@Column(name = "rolle")
|
||||
private List<RolleEnum> rollen = new ArrayList<>();
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "sessionId", nullable = false)
|
||||
private SessionEntity session;
|
||||
@OneToMany(mappedBy = "mitspieler", fetch = FetchType.EAGER)
|
||||
private List<AktiveSperreEntity> aktiveSperren = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MitspielerEntity[mitspielerId=" + mitspielerId + ", name=" + name
|
||||
+ ", geschlecht=" + geschlecht + ", rollen=" + rollen + ", werkzeuge=" + werkzeuge + "]";
|
||||
}
|
||||
|
||||
public Mitspieler toMitspieler() {
|
||||
Mitspieler mitspieler = new Mitspieler();
|
||||
mitspieler.setGeschlecht(geschlecht);
|
||||
mitspieler.setId(mitspielerId);
|
||||
mitspieler.setName(name);
|
||||
mitspieler.setRollen(rollen);
|
||||
mitspieler.setSpieltMit(spieltMit);
|
||||
mitspieler.setVerfuegbareWerkzeuge(new ArrayList<>(werkzeuge));
|
||||
return mitspieler;
|
||||
}
|
||||
}
|
||||
package de.oaa.xxx.games.bdsm.entity;
|
||||
|
||||
import de.oaa.xxx.games.bdsm.GeschlechtEnum;
|
||||
import de.oaa.xxx.games.bdsm.Mitspieler;
|
||||
import de.oaa.xxx.games.bdsm.RolleEnum;
|
||||
import de.oaa.xxx.games.bdsm.Werkzeug;
|
||||
import jakarta.persistence.CollectionTable;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "mitspieler")
|
||||
public class MitspielerEntity {
|
||||
|
||||
@Id
|
||||
@Column
|
||||
private UUID mitspielerId;
|
||||
@Column
|
||||
private UUID userId;
|
||||
@Column
|
||||
private boolean eigenesGeraet;
|
||||
@Column
|
||||
private String name;
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column
|
||||
private GeschlechtEnum geschlecht;
|
||||
@Enumerated(EnumType.STRING)
|
||||
@ElementCollection(targetClass = Werkzeug.class, fetch = FetchType.EAGER)
|
||||
@CollectionTable(name = "mitspieler_werkzeuge", joinColumns = @JoinColumn(name = "mitspielerId"))
|
||||
@Column(name = "werkzeug")
|
||||
private List<Werkzeug> werkzeuge = new ArrayList<>();
|
||||
@Enumerated(EnumType.STRING)
|
||||
@ElementCollection(targetClass = GeschlechtEnum.class, fetch = FetchType.EAGER)
|
||||
@CollectionTable(name = "mitspieler_spieltMit", joinColumns = @JoinColumn(name = "mitspielerId"))
|
||||
@Column(name = "geschlecht")
|
||||
private List<GeschlechtEnum> spieltMit = new ArrayList<>();
|
||||
@Enumerated(EnumType.STRING)
|
||||
@ElementCollection(targetClass = RolleEnum.class, fetch = FetchType.EAGER)
|
||||
@CollectionTable(name = "mitspieler_rollen", joinColumns = @JoinColumn(name = "mitspielerId"))
|
||||
@Column(name = "rolle")
|
||||
private List<RolleEnum> rollen = new ArrayList<>();
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "sessionId", nullable = false)
|
||||
private BdsmGameEntity session;
|
||||
@OneToMany(mappedBy = "mitspieler", fetch = FetchType.EAGER)
|
||||
private List<AktiveSperreEntity> aktiveSperren = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MitspielerEntity[mitspielerId=" + mitspielerId + ", name=" + name
|
||||
+ ", geschlecht=" + geschlecht + ", rollen=" + rollen + ", werkzeuge=" + werkzeuge + "]";
|
||||
}
|
||||
|
||||
public Mitspieler toMitspieler() {
|
||||
Mitspieler mitspieler = new Mitspieler();
|
||||
mitspieler.setGeschlecht(geschlecht);
|
||||
mitspieler.setId(mitspielerId);
|
||||
mitspieler.setUserId(userId);
|
||||
mitspieler.setEigenesGeraet(eigenesGeraet);
|
||||
mitspieler.setName(name);
|
||||
mitspieler.setRollen(rollen);
|
||||
mitspieler.setSpieltMit(spieltMit);
|
||||
mitspieler.setVerfuegbareWerkzeuge(new ArrayList<>(werkzeuge));
|
||||
return mitspieler;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package de.oaa.xxx.session.repository;
|
||||
package de.oaa.xxx.games.bdsm.repository;
|
||||
|
||||
import de.oaa.xxx.session.entity.AktiveSperreEntity;
|
||||
import de.oaa.xxx.games.bdsm.entity.AktiveSperreEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.oaa.xxx.games.bdsm.repository;
|
||||
|
||||
import de.oaa.xxx.games.bdsm.entity.BdsmDefaultsEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface BdsmDefaultsRepository extends JpaRepository<BdsmDefaultsEntity, UUID> {
|
||||
Optional<BdsmDefaultsEntity> findByUserId(UUID userId);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.oaa.xxx.games.bdsm.repository;
|
||||
|
||||
import de.oaa.xxx.games.bdsm.entity.BdsmEinladungEntity;
|
||||
import de.oaa.xxx.games.bdsm.entity.BdsmEinladungEntity.Status;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface BdsmEinladungRepository extends JpaRepository<BdsmEinladungEntity, UUID> {
|
||||
|
||||
List<BdsmEinladungEntity> findBySetupId(UUID setupId);
|
||||
|
||||
List<BdsmEinladungEntity> findByInviteeIdAndStatus(UUID inviteeId, Status status);
|
||||
|
||||
List<BdsmEinladungEntity> findByInviterIdAndStatus(UUID inviterId, Status status);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.oaa.xxx.games.bdsm.repository;
|
||||
|
||||
import de.oaa.xxx.games.bdsm.entity.BdsmGameEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface BdsmGameRepository extends JpaRepository<BdsmGameEntity, UUID> {
|
||||
|
||||
Optional<BdsmGameEntity> findByUserId(UUID userId);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package de.oaa.xxx.session.repository;
|
||||
package de.oaa.xxx.games.bdsm.repository;
|
||||
|
||||
import de.oaa.xxx.session.entity.MitspielerEntity;
|
||||
import de.oaa.xxx.games.bdsm.entity.MitspielerEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.UUID;
|
||||
@@ -1,6 +1,6 @@
|
||||
package de.oaa.xxx.session.sperre;
|
||||
package de.oaa.xxx.games.bdsm.sperre;
|
||||
|
||||
import de.oaa.xxx.session.Callback;
|
||||
import de.oaa.xxx.games.bdsm.Callback;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package de.oaa.xxx.session.sperre;
|
||||
package de.oaa.xxx.games.bdsm.sperre;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import de.oaa.xxx.session.aufgaben.AufgabenList;
|
||||
import de.oaa.xxx.session.aufgaben.Sperre;
|
||||
import de.oaa.xxx.session.entity.AktiveSperreEntity;
|
||||
import de.oaa.xxx.session.entity.MitspielerEntity;
|
||||
import de.oaa.xxx.session.entity.SessionEntity;
|
||||
import de.oaa.xxx.session.repository.AktiveSperreRepository;
|
||||
import de.oaa.xxx.session.repository.MitspielerRepository;
|
||||
import de.oaa.xxx.session.repository.SessionRepository;
|
||||
import de.oaa.xxx.games.bdsm.aufgaben.AufgabenList;
|
||||
import de.oaa.xxx.games.bdsm.aufgaben.Sperre;
|
||||
import de.oaa.xxx.games.bdsm.entity.AktiveSperreEntity;
|
||||
import de.oaa.xxx.games.bdsm.entity.MitspielerEntity;
|
||||
import de.oaa.xxx.games.bdsm.entity.BdsmGameEntity;
|
||||
import de.oaa.xxx.games.bdsm.repository.AktiveSperreRepository;
|
||||
import de.oaa.xxx.games.bdsm.repository.MitspielerRepository;
|
||||
import de.oaa.xxx.games.bdsm.repository.BdsmGameRepository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
@@ -19,9 +19,9 @@ public class SperreVerarbeiten {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public void sperreAnwenden(SperreCallback callback, SessionRepository sessionRepository,
|
||||
public void sperreAnwenden(SperreCallback callback, BdsmGameRepository sessionRepository,
|
||||
MitspielerRepository mitspielerRepository, AktiveSperreRepository sperreRepository) throws Exception {
|
||||
SessionEntity session = sessionRepository.findById(callback.getSessionId()).orElse(null);
|
||||
BdsmGameEntity session = sessionRepository.findById(callback.getSessionId()).orElse(null);
|
||||
MitspielerEntity mitspieler = mitspielerRepository.findById(callback.getSpielerId()).orElse(null);
|
||||
if (session != null) {
|
||||
AufgabenList aufgaben = objectMapper.readValue(session.getAufgaben(), AufgabenList.class);
|
||||
@@ -56,7 +56,7 @@ public class SperreVerarbeiten {
|
||||
sperreRepository.save(verlaengern);
|
||||
}
|
||||
|
||||
private void fill(SperreCallback callback, SessionEntity session, MitspielerEntity mitspieler,
|
||||
private void fill(SperreCallback callback, BdsmGameEntity session, MitspielerEntity mitspieler,
|
||||
Sperre sperre, AktiveSperreEntity aktiv) {
|
||||
aktiv.setAktiveSperreId(UUID.randomUUID());
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
@@ -70,7 +70,7 @@ public class SperreVerarbeiten {
|
||||
aktiv.setReleaseText(callback.getReleaseText());
|
||||
}
|
||||
|
||||
private Integer berechneDauer(SessionEntity session, Sperre sperre) {
|
||||
private Integer berechneDauer(BdsmGameEntity session, Sperre sperre) {
|
||||
Integer minuten = 30;
|
||||
if (sperre.getMinutenVon() != null) {
|
||||
if (sperre.getMinutenBis() != null) {
|
||||
@@ -1,6 +1,6 @@
|
||||
package de.oaa.xxx.session.sperre;
|
||||
package de.oaa.xxx.games.bdsm.sperre;
|
||||
|
||||
import de.oaa.xxx.session.Callback;
|
||||
import de.oaa.xxx.games.bdsm.Callback;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -22,9 +22,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import de.oaa.xxx.games.chastity.cardlock.CardlockRepository;
|
||||
import de.oaa.xxx.social.SseService;
|
||||
import de.oaa.xxx.social.entity.MessageEntity;
|
||||
import de.oaa.xxx.social.repository.MessageRepository;
|
||||
import de.oaa.xxx.social.SystemMessageService;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
|
||||
@RestController
|
||||
@@ -34,8 +32,7 @@ public class LockeeInvitationController {
|
||||
private final LockeeInvitationRepository lockeeInvitationRepository;
|
||||
private final CardlockRepository cardlockRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final MessageRepository messageRepository;
|
||||
private final SseService sseService;
|
||||
private final SystemMessageService systemMessageService;
|
||||
|
||||
@Value("${app.base-url:http://localhost:8080}")
|
||||
private String baseUrl;
|
||||
@@ -45,27 +42,15 @@ public class LockeeInvitationController {
|
||||
public LockeeInvitationController(LockeeInvitationRepository lockeeInvitationRepository,
|
||||
CardlockRepository cardlockRepository,
|
||||
UserRepository userRepository,
|
||||
MessageRepository messageRepository,
|
||||
SseService sseService) {
|
||||
SystemMessageService systemMessageService) {
|
||||
this.lockeeInvitationRepository = lockeeInvitationRepository;
|
||||
this.cardlockRepository = cardlockRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.messageRepository = messageRepository;
|
||||
this.sseService = sseService;
|
||||
this.systemMessageService = systemMessageService;
|
||||
}
|
||||
|
||||
private void sendMessage(UUID senderId, UUID receiverId, String text, String targetUrl) {
|
||||
MessageEntity msg = new MessageEntity();
|
||||
msg.setMessageId(UUID.randomUUID());
|
||||
msg.setSenderId(senderId);
|
||||
msg.setReceiverId(receiverId);
|
||||
msg.setText(text);
|
||||
msg.setSentAt(LocalDateTime.now());
|
||||
msg.setSystemMessage(true);
|
||||
if (targetUrl != null) msg.setTargetUrl(targetUrl);
|
||||
messageRepository.save(msg);
|
||||
long unread = messageRepository.countByReceiverIdAndSystemMessageAndReadAtIsNull(receiverId, true);
|
||||
sseService.push(receiverId, "NOTIFICATION", java.util.Map.of("unreadCount", unread, "text", text));
|
||||
private void sendMessage(UUID senderId, UUID receiverId, String text, String targetUrl, de.oaa.xxx.social.entity.MessageCause cause) {
|
||||
systemMessageService.send(senderId, receiverId, text, targetUrl, cause);
|
||||
}
|
||||
|
||||
private String generateUnlockCode(int lines) {
|
||||
@@ -154,7 +139,7 @@ public class LockeeInvitationController {
|
||||
String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock";
|
||||
sendMessage(myId, inv.getLockeeUserId(),
|
||||
me.getName() + " hat die Lockee-Einladung für das Lock „" + lockName + "\" zurückgezogen.",
|
||||
null);
|
||||
null, de.oaa.xxx.social.entity.MessageCause.INVITATION);
|
||||
}
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
@@ -248,7 +233,7 @@ public class LockeeInvitationController {
|
||||
String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock";
|
||||
sendMessage(myId, inv.getKeyholderUserId(),
|
||||
me.getName() + " hat die Einladung als Lockee für das Lock „" + lockName + "\" angenommen.",
|
||||
"/keyholder.html");
|
||||
"/keyholder.html", de.oaa.xxx.social.entity.MessageCause.INVITATION);
|
||||
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"lockId", lock.getLockId().toString(),
|
||||
@@ -278,7 +263,7 @@ public class LockeeInvitationController {
|
||||
String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock";
|
||||
sendMessage(myId, inv.getKeyholderUserId(),
|
||||
me.getName() + " hat die Einladung als Lockee für das Lock „" + lockName + "\" abgelehnt.",
|
||||
null);
|
||||
null, de.oaa.xxx.social.entity.MessageCause.INVITATION);
|
||||
}
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
|
||||
@@ -41,7 +41,7 @@ import de.oaa.xxx.games.chastity.KeyholderInvitationEntity;
|
||||
import de.oaa.xxx.games.chastity.KeyholderInvitationRepository;
|
||||
import de.oaa.xxx.games.chastity.LockeeInvitationEntity;
|
||||
import de.oaa.xxx.games.chastity.LockeeInvitationRepository;
|
||||
import de.oaa.xxx.games.chastity.history.LockHistoryRepository;
|
||||
import de.oaa.xxx.games.history.GameHistoryRepository;
|
||||
import de.oaa.xxx.games.chastity.tasks.AssignedTaskEntity;
|
||||
import de.oaa.xxx.games.chastity.tasks.AssignedTaskRepository;
|
||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||
@@ -49,9 +49,7 @@ import de.oaa.xxx.games.chastity.verification.VerificationEntity;
|
||||
import de.oaa.xxx.games.chastity.verification.VerificationRepository;
|
||||
import de.oaa.xxx.games.chastity.verification.VerificationVoteEntity;
|
||||
import de.oaa.xxx.games.chastity.verification.VerificationVoteRepository;
|
||||
import de.oaa.xxx.social.SseService;
|
||||
import de.oaa.xxx.social.entity.MessageEntity;
|
||||
import de.oaa.xxx.social.repository.MessageRepository;
|
||||
import de.oaa.xxx.social.SystemMessageService;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
|
||||
@RestController
|
||||
@@ -65,15 +63,14 @@ public class CardLockController {
|
||||
private final VerificationRepository verificationRepository;
|
||||
private final VerificationVoteRepository verificationVoteRepository;
|
||||
private final HygieneViolationRepository hygieneViolationRepository;
|
||||
private final MessageRepository messageRepository;
|
||||
private final LockeeInvitationRepository lockeeInvitationRepository;
|
||||
private final AssignedTaskRepository assignedTaskRepository;
|
||||
private final KeyholderTaskChoiceRepository keyholderTaskChoiceRepository;
|
||||
private final CommunityTaskVoteRepository communityTaskVoteRepository;
|
||||
private final UnlockCodeHistoryRepository unlockCodeHistoryRepository;
|
||||
private final UnlockCodeHistoryService unlockCodeHistoryService;
|
||||
private final LockHistoryRepository lockHistoryRepository;
|
||||
private final SseService sseService;
|
||||
private final GameHistoryRepository gameHistoryRepository;
|
||||
private final SystemMessageService systemMessageService;
|
||||
|
||||
@Value("${app.base-url:http://localhost:8080}")
|
||||
private String baseUrl;
|
||||
@@ -85,15 +82,14 @@ public class CardLockController {
|
||||
VerificationRepository verificationRepository,
|
||||
VerificationVoteRepository verificationVoteRepository,
|
||||
HygieneViolationRepository hygieneViolationRepository,
|
||||
MessageRepository messageRepository,
|
||||
LockeeInvitationRepository lockeeInvitationRepository,
|
||||
AssignedTaskRepository assignedTaskRepository,
|
||||
KeyholderTaskChoiceRepository keyholderTaskChoiceRepository,
|
||||
CommunityTaskVoteRepository communityTaskVoteRepository,
|
||||
UnlockCodeHistoryRepository unlockCodeHistoryRepository,
|
||||
UnlockCodeHistoryService unlockCodeHistoryService,
|
||||
LockHistoryRepository lockHistoryRepository,
|
||||
SseService sseService) {
|
||||
GameHistoryRepository gameHistoryRepository,
|
||||
SystemMessageService systemMessageService) {
|
||||
this.cardlockRepository = cardlockRepository;
|
||||
this.cardLockRepository = cardLockRepository;
|
||||
this.userRepository = userRepository;
|
||||
@@ -101,15 +97,14 @@ public class CardLockController {
|
||||
this.verificationRepository = verificationRepository;
|
||||
this.verificationVoteRepository = verificationVoteRepository;
|
||||
this.hygieneViolationRepository = hygieneViolationRepository;
|
||||
this.messageRepository = messageRepository;
|
||||
this.lockeeInvitationRepository = lockeeInvitationRepository;
|
||||
this.assignedTaskRepository = assignedTaskRepository;
|
||||
this.keyholderTaskChoiceRepository = keyholderTaskChoiceRepository;
|
||||
this.communityTaskVoteRepository = communityTaskVoteRepository;
|
||||
this.unlockCodeHistoryRepository = unlockCodeHistoryRepository;
|
||||
this.unlockCodeHistoryService = unlockCodeHistoryService;
|
||||
this.lockHistoryRepository = lockHistoryRepository;
|
||||
this.sseService = sseService;
|
||||
this.gameHistoryRepository = gameHistoryRepository;
|
||||
this.systemMessageService = systemMessageService;
|
||||
}
|
||||
|
||||
record CreateCardLockRequest(
|
||||
@@ -194,7 +189,7 @@ public class CardLockController {
|
||||
String lockName = req.name() != null && !req.name().isBlank() ? req.name() : "Unbenanntes Lock";
|
||||
sendMessage(myId, lockee.getUserId(),
|
||||
me.getName() + " hat dich als Lockee für das Lock „" + lockName + "\" eingeladen.",
|
||||
"/einladungen.html");
|
||||
"/einladungen.html", de.oaa.xxx.social.entity.MessageCause.INVITATION);
|
||||
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"lockId", lock.getLockId().toString(),
|
||||
@@ -257,7 +252,7 @@ public class CardLockController {
|
||||
String lockName = req.name() != null && !req.name().isBlank() ? req.name() : "Unbenanntes Lock";
|
||||
sendMessage(me.getUserId(), kh.getUserId(),
|
||||
me.getName() + " hat dich als Keyholder*In für das Lock „" + lockName + "\" eingeladen.",
|
||||
"/einladungen.html");
|
||||
"/einladungen.html", de.oaa.xxx.social.entity.MessageCause.INVITATION);
|
||||
|
||||
keyholderPending = true;
|
||||
}
|
||||
@@ -282,7 +277,7 @@ public class CardLockController {
|
||||
var l = lockOpt.get();
|
||||
if (!l.getLockee().equals(myId)) return ResponseEntity.status(403).build();
|
||||
|
||||
CardLockService service = new CardLockService(l, verificationRepository, verificationVoteRepository, cardLockRepository, lockHistoryRepository, userRepository);
|
||||
CardLockService service = new CardLockService(l, verificationRepository, verificationVoteRepository, cardLockRepository, gameHistoryRepository, userRepository);
|
||||
CardDTO dto = service.getNextCard();
|
||||
if (dto == null) return ResponseEntity.status(409).body(Map.of("error", "Keine Karte verfügbar"));
|
||||
|
||||
@@ -300,7 +295,7 @@ public class CardLockController {
|
||||
userRepository.findById(l.getKeyholder()).ifPresent(kh ->
|
||||
sendMessage(l.getLockee(), kh.getUserId(),
|
||||
"Deine Lockee hat eine Aufgaben-Karte gezogen – wähle eine Aufgabe aus.",
|
||||
"/keyholder.html"));
|
||||
"/keyholder.html", de.oaa.xxx.social.entity.MessageCause.GAME_STATE));
|
||||
taskPending = "KEYHOLDER";
|
||||
|
||||
} else if ("COMMUNITY".equals(l.getTaskCardMode())) {
|
||||
@@ -320,9 +315,14 @@ public class CardLockController {
|
||||
result.put("unlockCode", dto.unlockCode() != null ? dto.unlockCode() : "");
|
||||
if (taskPending != null) result.put("taskPending", taskPending);
|
||||
|
||||
// Grüne Karte → Entsperrcode-Historie speichern
|
||||
// Grüne Karte → Entsperrcode-Historie speichern + Keyholder benachrichtigen
|
||||
if (dto.unlockCode() != null && !dto.unlockCode().isBlank()) {
|
||||
unlockCodeHistoryService.save(myId, l.getLockId(), l.getName(), dto.unlockCode(), "GREEN_CARD");
|
||||
if (l.getKeyholder() != null) {
|
||||
sendMessage(myId, l.getKeyholder(),
|
||||
meOpt.get().getName() + " hat die grüne Karte gezogen! Der Entsperrcode wurde angezeigt.",
|
||||
"/keyholder.html", de.oaa.xxx.social.entity.MessageCause.GAME_STATE);
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(result);
|
||||
@@ -417,7 +417,7 @@ public class CardLockController {
|
||||
var l = lockOpt.get();
|
||||
if (!l.getLockee().equals(myId)) return ResponseEntity.status(403).build();
|
||||
|
||||
CardLockService service = new CardLockService(l, verificationRepository, verificationVoteRepository, cardLockRepository, lockHistoryRepository, userRepository);
|
||||
CardLockService service = new CardLockService(l, verificationRepository, verificationVoteRepository, cardLockRepository, gameHistoryRepository, userRepository);
|
||||
service.clearTask();
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
@@ -434,8 +434,16 @@ public class CardLockController {
|
||||
var l = lockOpt.get();
|
||||
if (!l.getLockee().equals(myId)) return ResponseEntity.status(403).build();
|
||||
|
||||
CardLockService service = new CardLockService(l, verificationRepository, verificationVoteRepository, cardLockRepository, lockHistoryRepository, userRepository);
|
||||
CardLockService service = new CardLockService(l, verificationRepository, verificationVoteRepository, cardLockRepository, gameHistoryRepository, userRepository);
|
||||
service.putBackGreen();
|
||||
|
||||
// Grüne Karte zurückgelegt → Keyholder benachrichtigen
|
||||
if (l.getKeyholder() != null) {
|
||||
sendMessage(myId, l.getKeyholder(),
|
||||
meOpt.get().getName() + " hat die grüne Karte zurückgelegt und bleibt im Lock.",
|
||||
"/keyholder.html", de.oaa.xxx.social.entity.MessageCause.GAME_STATE);
|
||||
}
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@@ -557,7 +565,7 @@ public class CardLockController {
|
||||
lockDirty = true;
|
||||
sendMessage(l.getKeyholder(), l.getLockee(),
|
||||
"Die dir gestellte Aufgabe ist abgelaufen, ohne dass du reagiert hast. Die Strafe wurde automatisch angewendet.",
|
||||
"/activelock.html?lockId=" + l.getLockId());
|
||||
"/activelock.html?lockId=" + l.getLockId(), de.oaa.xxx.social.entity.MessageCause.GAME_STATE);
|
||||
}
|
||||
if (lockDirty) cardlockRepository.save(l);
|
||||
|
||||
@@ -695,7 +703,7 @@ public class CardLockController {
|
||||
var lockee = meOpt.get();
|
||||
sendMessage(myId, lock.getKeyholder(),
|
||||
"📸 " + lockee.getName() + " hat eine Verifikation eingereicht.",
|
||||
"/keyholder.html");
|
||||
"/keyholder.html", de.oaa.xxx.social.entity.MessageCause.GAME_STATE);
|
||||
}
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
@@ -793,14 +801,9 @@ public class CardLockController {
|
||||
if (lockOpt.isPresent()) {
|
||||
var lock = lockOpt.get();
|
||||
String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock";
|
||||
MessageEntity msg = new MessageEntity();
|
||||
msg.setMessageId(UUID.randomUUID());
|
||||
msg.setSenderId(myId);
|
||||
msg.setReceiverId(lock.getLockee());
|
||||
msg.setText(me.getName() + " hat die Einladung als Keyholder*In für das Lock „" + lockName + "\" abgelehnt.");
|
||||
msg.setSentAt(LocalDateTime.now());
|
||||
msg.setSystemMessage(true);
|
||||
messageRepository.save(msg);
|
||||
sendMessage(myId, lock.getLockee(),
|
||||
me.getName() + " hat die Einladung als Keyholder*In für das Lock „" + lockName + "\" abgelehnt.",
|
||||
null, de.oaa.xxx.social.entity.MessageCause.INVITATION);
|
||||
}
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
@@ -855,14 +858,9 @@ public class CardLockController {
|
||||
|
||||
String lockName = lockOpt.get().getName() != null && !lockOpt.get().getName().isBlank()
|
||||
? lockOpt.get().getName() : "Unbenanntes Lock";
|
||||
MessageEntity msg = new MessageEntity();
|
||||
msg.setMessageId(UUID.randomUUID());
|
||||
msg.setSenderId(myId);
|
||||
msg.setReceiverId(inv.getKeyholderUserId());
|
||||
msg.setText(me.getName() + " hat die Keyholder-Einladung für das Lock „" + lockName + "\" zurückgezogen.");
|
||||
msg.setSentAt(LocalDateTime.now());
|
||||
msg.setSystemMessage(true);
|
||||
messageRepository.save(msg);
|
||||
sendMessage(myId, inv.getKeyholderUserId(),
|
||||
me.getName() + " hat die Keyholder-Einladung für das Lock „" + lockName + "\" zurückgezogen.",
|
||||
null, de.oaa.xxx.social.entity.MessageCause.INVITATION);
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
@@ -1069,7 +1067,7 @@ public class CardLockController {
|
||||
if (!l.getLockee().equals(myId)) return ResponseEntity.status(403).build();
|
||||
|
||||
// Entsperrung protokollieren (History + XP) – gültig nur wenn Keyholder vorhanden und kein Auto-Notfall
|
||||
CardLockService service = new CardLockService(l, verificationRepository, verificationVoteRepository, cardLockRepository, lockHistoryRepository, userRepository);
|
||||
CardLockService service = new CardLockService(l, verificationRepository, verificationVoteRepository, cardLockRepository, gameHistoryRepository, userRepository);
|
||||
service.unlock(l.getUnlockCode());
|
||||
|
||||
var verifications = verificationRepository.findByLockId(lockId);
|
||||
@@ -1124,14 +1122,8 @@ public class CardLockController {
|
||||
? me.getName() + " hat " + toAdd.size() + " Karte(n) zu deinem Lock hinzugefügt: " + detail + "."
|
||||
: me.getName() + " hat Karten zu deinem Lock hinzugefügt.";
|
||||
|
||||
MessageEntity msg = new MessageEntity();
|
||||
msg.setMessageId(UUID.randomUUID());
|
||||
msg.setSenderId(myId);
|
||||
msg.setReceiverId(l.getLockee());
|
||||
msg.setText(msgText);
|
||||
msg.setSentAt(LocalDateTime.now());
|
||||
msg.setSystemMessage(true);
|
||||
messageRepository.save(msg);
|
||||
sendMessage(myId, l.getLockee(), msgText, "/activelock.html?lockId=" + lockId,
|
||||
de.oaa.xxx.social.entity.MessageCause.GAME_STATE);
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
@@ -1189,14 +1181,8 @@ public class CardLockController {
|
||||
? me.getName() + " hat " + removed.size() + " Karte(n) aus deinem Lock entfernt: " + detail + "."
|
||||
: me.getName() + " hat Karten aus deinem Lock entfernt.";
|
||||
|
||||
MessageEntity msg = new MessageEntity();
|
||||
msg.setMessageId(UUID.randomUUID());
|
||||
msg.setSenderId(myId);
|
||||
msg.setReceiverId(l.getLockee());
|
||||
msg.setText(msgText);
|
||||
msg.setSentAt(LocalDateTime.now());
|
||||
msg.setSystemMessage(true);
|
||||
messageRepository.save(msg);
|
||||
sendMessage(myId, l.getLockee(), msgText, "/activelock.html?lockId=" + lockId,
|
||||
de.oaa.xxx.social.entity.MessageCause.GAME_STATE);
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
@@ -1221,19 +1207,8 @@ public class CardLockController {
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessage(UUID senderId, UUID receiverId, String text, String targetUrl) {
|
||||
if (senderId == null || receiverId == null) return;
|
||||
MessageEntity msg = new MessageEntity();
|
||||
msg.setMessageId(UUID.randomUUID());
|
||||
msg.setSenderId(senderId);
|
||||
msg.setReceiverId(receiverId);
|
||||
msg.setText(text);
|
||||
msg.setSentAt(LocalDateTime.now());
|
||||
msg.setSystemMessage(true);
|
||||
if (targetUrl != null) msg.setTargetUrl(targetUrl);
|
||||
messageRepository.save(msg);
|
||||
long unread = messageRepository.countByReceiverIdAndSystemMessageAndReadAtIsNull(receiverId, true);
|
||||
sseService.push(receiverId, "NOTIFICATION", java.util.Map.of("unreadCount", unread, "text", text));
|
||||
private void sendMessage(UUID senderId, UUID receiverId, String text, String targetUrl, de.oaa.xxx.social.entity.MessageCause cause) {
|
||||
systemMessageService.send(senderId, receiverId, text, targetUrl, cause);
|
||||
}
|
||||
|
||||
@GetMapping("/cardlock/unlock-history")
|
||||
@@ -1305,7 +1280,7 @@ public class CardLockController {
|
||||
sendMessage(me.getUserId(), l.getLockee(),
|
||||
me.getName() + " hat dir eine Aufgabe gestellt. Du hast " +
|
||||
req.acceptDeadlineMinutes() + " Minuten, um sie anzunehmen.",
|
||||
"/activelock.html?lockId=" + lockId);
|
||||
"/activelock.html?lockId=" + lockId, de.oaa.xxx.social.entity.MessageCause.GAME_STATE);
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
@@ -1365,7 +1340,7 @@ public class CardLockController {
|
||||
assignedTaskRepository.save(task);
|
||||
cardlockRepository.save(l);
|
||||
|
||||
sendMessage(myId, l.getKeyholder(), meOpt.get().getName() + " hat die gestellte Aufgabe angenommen.", "/keyholder.html");
|
||||
sendMessage(myId, l.getKeyholder(), meOpt.get().getName() + " hat die gestellte Aufgabe angenommen.", "/keyholder.html", de.oaa.xxx.social.entity.MessageCause.GAME_STATE);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@@ -1397,7 +1372,7 @@ public class CardLockController {
|
||||
assignedTaskRepository.save(task);
|
||||
cardlockRepository.save(l);
|
||||
|
||||
sendMessage(myId, l.getKeyholder(), meOpt.get().getName() + " hat die gestellte Aufgabe abgelehnt. Die Strafe wurde angewendet.", "/keyholder.html");
|
||||
sendMessage(myId, l.getKeyholder(), meOpt.get().getName() + " hat die gestellte Aufgabe abgelehnt. Die Strafe wurde angewendet.", "/keyholder.html", de.oaa.xxx.social.entity.MessageCause.GAME_STATE);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@@ -1465,7 +1440,7 @@ public class CardLockController {
|
||||
until.toLocalDate().format(java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy")) +
|
||||
" " + until.toLocalTime().format(java.time.format.DateTimeFormatter.ofPattern("HH:mm")) +
|
||||
" Uhr eingefroren.",
|
||||
"/activelock.html?lockId=" + lockId);
|
||||
"/activelock.html?lockId=" + lockId, de.oaa.xxx.social.entity.MessageCause.GAME_STATE);
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
@@ -1490,7 +1465,7 @@ public class CardLockController {
|
||||
cardlockRepository.save(l);
|
||||
|
||||
sendMessage(myId, l.getLockee(), me.getName() + " hat dein Lock wieder entfroren.",
|
||||
"/activelock.html?lockId=" + lockId);
|
||||
"/activelock.html?lockId=" + lockId, de.oaa.xxx.social.entity.MessageCause.GAME_STATE);
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
@@ -1512,7 +1487,7 @@ public class CardLockController {
|
||||
|
||||
sendMessage(myId, l.getLockee(),
|
||||
"Dein Keyholder hat das Lock freigeschaltet. Du erhältst beim nächsten Laden deinen Entsperrcode.",
|
||||
"/activelock.html?lockId=" + lockId);
|
||||
"/activelock.html?lockId=" + lockId, de.oaa.xxx.social.entity.MessageCause.GAME_STATE);
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
@@ -1542,7 +1517,7 @@ public class CardLockController {
|
||||
// Keyholderin benachrichtigen
|
||||
sendMessage(myId, l.getKeyholder(),
|
||||
"⚠️ NOTFALL: " + me.getName() + " bittet dringend um Freigabe des Locks. Bitte reagiere innerhalb einer Stunde, sonst öffnet sich das Lock automatisch.",
|
||||
"/keyholder.html");
|
||||
"/keyholder.html", de.oaa.xxx.social.entity.MessageCause.EMERGENCY);
|
||||
}
|
||||
cardlockRepository.save(l);
|
||||
return ResponseEntity.noContent().build();
|
||||
|
||||
@@ -11,10 +11,9 @@ import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import de.oaa.xxx.games.chastity.LockType;
|
||||
import de.oaa.xxx.games.chastity.ProcessLock;
|
||||
import de.oaa.xxx.games.chastity.history.LockHistoryEntity;
|
||||
import de.oaa.xxx.games.chastity.history.LockHistoryRepository;
|
||||
import de.oaa.xxx.games.history.GameHistoryEntity;
|
||||
import de.oaa.xxx.games.history.GameHistoryRepository;
|
||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||
import de.oaa.xxx.games.chastity.verification.VerificationEntity;
|
||||
import de.oaa.xxx.games.chastity.verification.VerificationRepository;
|
||||
@@ -29,15 +28,15 @@ public class CardLockService extends ProcessLock {
|
||||
private VerificationRepository verificationRepository;
|
||||
private VerificationVoteRepository verificationVoteRepository;
|
||||
private CardLockRepository cardLockRepository;
|
||||
private LockHistoryRepository lockHistoryRepository;
|
||||
private GameHistoryRepository gameHistoryRepository;
|
||||
private UserRepository userRepository;
|
||||
|
||||
public CardLockService(CardLockEntity lock, VerificationRepository verificationRepository, VerificationVoteRepository verificationVoteRepository, CardLockRepository cardLockRepository, LockHistoryRepository lockHistoryRepository, UserRepository userRepository) {
|
||||
public CardLockService(CardLockEntity lock, VerificationRepository verificationRepository, VerificationVoteRepository verificationVoteRepository, CardLockRepository cardLockRepository, GameHistoryRepository gameHistoryRepository, UserRepository userRepository) {
|
||||
this.lock = lock;
|
||||
this.verificationRepository = verificationRepository;
|
||||
this.verificationVoteRepository = verificationVoteRepository;
|
||||
this.cardLockRepository = cardLockRepository;
|
||||
this.lockHistoryRepository = lockHistoryRepository;
|
||||
this.gameHistoryRepository = gameHistoryRepository;
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@@ -101,28 +100,30 @@ public class CardLockService extends ProcessLock {
|
||||
|
||||
public void unlock(String unlockCode) {
|
||||
this.lock.setUnlockTime(LocalDateTime.now());
|
||||
// Self-Lock oder automatische Entsperrung ohne Keyholder-Zustimmung → ungültig
|
||||
boolean valid = lock.getKeyholder() != null && !lock.isEmergencyAutoUnlocked();
|
||||
if (!this.lock.isTestLock()) {
|
||||
if (Duration.between(lock.getStartTime(), lock.getUnlockTime()).toHours() > 24) {
|
||||
Set<LocalDate> verifications = verificationRepository.findByLockId(this.lock.getLockId()).stream()
|
||||
.filter(verification -> isValid(verification))
|
||||
.map(verification -> verification.getVerificationTime().toLocalDate())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
LocalDate current = this.lock.getStartTime().toLocalDate();
|
||||
LocalDate last = this.lock.getUnlockTime().toLocalDate().minusDays(1);
|
||||
|
||||
while (!current.isAfter(last)) {
|
||||
if (!verifications.contains(current)) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
current = current.plusDays(1);
|
||||
boolean valid = true;
|
||||
if (lock.isEmergencyAutoUnlocked()) {
|
||||
valid = false;
|
||||
LOGGER.debug("Lock invalid - Emergency Auto-Unlock (1h timer)");
|
||||
}
|
||||
if (lock.isTestLock()) {
|
||||
valid = false;
|
||||
} else if (Duration.between(lock.getStartTime(), lock.getUnlockTime()).toHours() > 24) {
|
||||
Set<LocalDate> verifications = verificationRepository.findByLockId(this.lock.getLockId()).stream()
|
||||
.filter(verification -> isValid(verification))
|
||||
.map(verification -> verification.getVerificationTime().toLocalDate())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
LocalDate current = this.lock.getStartTime().toLocalDate();
|
||||
LocalDate last = this.lock.getUnlockTime().toLocalDate().minusDays(1);
|
||||
|
||||
while (!current.isAfter(last)) {
|
||||
if (!verifications.contains(current)) {
|
||||
valid = false;
|
||||
LOGGER.debug("Lock invalid - no daily verification on %s", current.toString());
|
||||
break;
|
||||
}
|
||||
current = current.plusDays(1);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
lock.setUnlockTime(LocalDateTime.now());
|
||||
@@ -132,31 +133,18 @@ public class CardLockService extends ProcessLock {
|
||||
if (valid) {
|
||||
long durationMinutes = Duration.between(lock.getStartTime(), lock.getUnlockTime()).toMinutes();
|
||||
|
||||
// Eintrag für den Lockee
|
||||
LockHistoryEntity lockeeEntry = new LockHistoryEntity();
|
||||
lockeeEntry.setUserId(lock.getLockee());
|
||||
lockeeEntry.setLockedBy(lock.getKeyholder());
|
||||
lockeeEntry.setLockName(lock.getName());
|
||||
lockeeEntry.setStartTime(lock.getStartTime());
|
||||
lockeeEntry.setEndTime(lock.getUnlockTime());
|
||||
lockeeEntry.setType(LockType.CARD);
|
||||
lockeeEntry.setDurationMinutes(durationMinutes);
|
||||
lockeeEntry.setRole("LOCKEE");
|
||||
lockHistoryRepository.save(lockeeEntry);
|
||||
|
||||
// Eintrag für die Keyholderin
|
||||
// Gemeinsamer History-Eintrag mit Teilnehmerliste
|
||||
GameHistoryEntity entry = new GameHistoryEntity();
|
||||
entry.setGameType(de.oaa.xxx.games.history.GameType.CARDLOCK);
|
||||
entry.setGameName(lock.getName());
|
||||
entry.setStartTime(lock.getStartTime());
|
||||
entry.setEndTime(lock.getUnlockTime());
|
||||
entry.setDurationMinutes(durationMinutes);
|
||||
entry.addParticipant(lock.getLockee(), de.oaa.xxx.games.history.GameRole.LOCKEE);
|
||||
if (lock.getKeyholder() != null) {
|
||||
LockHistoryEntity khEntry = new LockHistoryEntity();
|
||||
khEntry.setUserId(lock.getKeyholder());
|
||||
khEntry.setLockedBy(lock.getLockee());
|
||||
khEntry.setLockName(lock.getName());
|
||||
khEntry.setStartTime(lock.getStartTime());
|
||||
khEntry.setEndTime(lock.getUnlockTime());
|
||||
khEntry.setType(LockType.CARD);
|
||||
khEntry.setDurationMinutes(durationMinutes);
|
||||
khEntry.setRole("KEYHOLDER");
|
||||
lockHistoryRepository.save(khEntry);
|
||||
entry.addParticipant(lock.getKeyholder(), de.oaa.xxx.games.history.GameRole.KEYHOLDER);
|
||||
}
|
||||
gameHistoryRepository.save(entry);
|
||||
|
||||
int minutes = (int) durationMinutes;
|
||||
userRepository.findById(lock.getLockee()).ifPresent(u -> {
|
||||
|
||||
@@ -3,10 +3,8 @@ package de.oaa.xxx.games.chastity.cardlock;
|
||||
import de.oaa.xxx.games.chastity.tasks.AssignedTaskEntity;
|
||||
import de.oaa.xxx.games.chastity.tasks.AssignedTaskRepository;
|
||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||
import de.oaa.xxx.social.SseService;
|
||||
import de.oaa.xxx.social.SystemMessageService;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
import de.oaa.xxx.social.entity.MessageEntity;
|
||||
import de.oaa.xxx.social.repository.MessageRepository;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -25,8 +23,7 @@ public class TaskCardController {
|
||||
private final CommunityTaskVoteRepository communityTaskVoteRepository;
|
||||
private final CommunityTaskVoteEntryRepository communityTaskVoteEntryRepository;
|
||||
private final AssignedTaskRepository assignedTaskRepository;
|
||||
private final MessageRepository messageRepository;
|
||||
private final SseService sseService;
|
||||
private final SystemMessageService systemMessageService;
|
||||
|
||||
public TaskCardController(CardlockRepository cardlockRepository,
|
||||
UserRepository userRepository,
|
||||
@@ -34,16 +31,14 @@ public class TaskCardController {
|
||||
CommunityTaskVoteRepository communityTaskVoteRepository,
|
||||
CommunityTaskVoteEntryRepository communityTaskVoteEntryRepository,
|
||||
AssignedTaskRepository assignedTaskRepository,
|
||||
MessageRepository messageRepository,
|
||||
SseService sseService) {
|
||||
SystemMessageService systemMessageService) {
|
||||
this.cardlockRepository = cardlockRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.keyholderTaskChoiceRepository = keyholderTaskChoiceRepository;
|
||||
this.communityTaskVoteRepository = communityTaskVoteRepository;
|
||||
this.communityTaskVoteEntryRepository = communityTaskVoteEntryRepository;
|
||||
this.assignedTaskRepository = assignedTaskRepository;
|
||||
this.messageRepository = messageRepository;
|
||||
this.sseService = sseService;
|
||||
this.systemMessageService = systemMessageService;
|
||||
}
|
||||
|
||||
// ── Keyholder: ausstehende Aufgaben-Karten-Entscheidungen ─────────────────
|
||||
@@ -240,16 +235,6 @@ public class TaskCardController {
|
||||
}
|
||||
|
||||
private void sendMessage(UUID fromId, UUID toId, String text, String targetUrl) {
|
||||
if (toId == null) return;
|
||||
MessageEntity msg = new MessageEntity();
|
||||
msg.setMessageId(java.util.UUID.randomUUID());
|
||||
msg.setSenderId(fromId);
|
||||
msg.setReceiverId(toId);
|
||||
msg.setText(text);
|
||||
msg.setSystemMessage(true);
|
||||
msg.setTargetUrl(targetUrl);
|
||||
msg.setSentAt(java.time.LocalDateTime.now());
|
||||
messageRepository.save(msg);
|
||||
sseService.push(toId, "notification", Map.of("text", text));
|
||||
systemMessageService.send(fromId, toId, text, targetUrl, de.oaa.xxx.social.entity.MessageCause.GAME_STATE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -17,9 +16,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import de.oaa.xxx.games.chastity.tasks.AssignedTaskEntity;
|
||||
import de.oaa.xxx.games.chastity.tasks.AssignedTaskRepository;
|
||||
import de.oaa.xxx.games.chastity.tasks.Task;
|
||||
import de.oaa.xxx.social.SseService;
|
||||
import de.oaa.xxx.social.entity.MessageEntity;
|
||||
import de.oaa.xxx.social.repository.MessageRepository;
|
||||
import de.oaa.xxx.social.SystemMessageService;
|
||||
|
||||
@Component
|
||||
public class TaskVoteScheduler {
|
||||
@@ -30,21 +27,18 @@ public class TaskVoteScheduler {
|
||||
private final CommunityTaskVoteEntryRepository communityTaskVoteEntryRepository;
|
||||
private final CardlockRepository cardlockRepository;
|
||||
private final AssignedTaskRepository assignedTaskRepository;
|
||||
private final MessageRepository messageRepository;
|
||||
private final SseService sseService;
|
||||
private final SystemMessageService systemMessageService;
|
||||
|
||||
public TaskVoteScheduler(CommunityTaskVoteRepository communityTaskVoteRepository,
|
||||
CommunityTaskVoteEntryRepository communityTaskVoteEntryRepository,
|
||||
CardlockRepository cardlockRepository,
|
||||
AssignedTaskRepository assignedTaskRepository,
|
||||
MessageRepository messageRepository,
|
||||
SseService sseService) {
|
||||
SystemMessageService systemMessageService) {
|
||||
this.communityTaskVoteRepository = communityTaskVoteRepository;
|
||||
this.communityTaskVoteEntryRepository = communityTaskVoteEntryRepository;
|
||||
this.cardlockRepository = cardlockRepository;
|
||||
this.assignedTaskRepository = assignedTaskRepository;
|
||||
this.messageRepository = messageRepository;
|
||||
this.sseService = sseService;
|
||||
this.systemMessageService = systemMessageService;
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = 60_000)
|
||||
@@ -117,16 +111,6 @@ public class TaskVoteScheduler {
|
||||
}
|
||||
|
||||
private void sendMessage(UUID toId, String text, String targetUrl) {
|
||||
if (toId == null) return;
|
||||
MessageEntity msg = new MessageEntity();
|
||||
msg.setMessageId(UUID.randomUUID());
|
||||
msg.setSenderId(toId); // System-Nachricht, kein echter Sender
|
||||
msg.setReceiverId(toId);
|
||||
msg.setText(text);
|
||||
msg.setSystemMessage(true);
|
||||
msg.setTargetUrl(targetUrl);
|
||||
msg.setSentAt(LocalDateTime.now());
|
||||
messageRepository.save(msg);
|
||||
sseService.push(toId, "notification", Map.of("text", text));
|
||||
systemMessageService.send(toId, toId, text, targetUrl, de.oaa.xxx.social.entity.MessageCause.GAME_STATE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
package de.oaa.xxx.games.chastity.history;
|
||||
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/lockhistory")
|
||||
public class LockHistoryController {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final LockHistoryRepository lockHistoryRepository;
|
||||
|
||||
public LockHistoryController(UserRepository userRepository, LockHistoryRepository lockHistoryRepository) {
|
||||
this.userRepository = userRepository;
|
||||
this.lockHistoryRepository = lockHistoryRepository;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<Map<String, Object>>> get(@RequestParam UUID userId, Principal principal) {
|
||||
var meOpt = userRepository.findByEmail(principal.getName());
|
||||
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||
|
||||
var result = lockHistoryRepository.findByUserIdOrderByEndTimeDesc(userId).stream()
|
||||
.map(e -> {
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("role", e.getRole());
|
||||
item.put("lockName", e.getLockName() != null ? e.getLockName() : "");
|
||||
item.put("startTime", e.getStartTime().toString());
|
||||
item.put("unlockTime", e.getEndTime().toString());
|
||||
item.put("durationMinutes", e.getDurationMinutes());
|
||||
if (e.getLockedBy() != null) {
|
||||
userRepository.findById(e.getLockedBy()).ifPresent(u -> {
|
||||
if ("LOCKEE".equals(e.getRole())) {
|
||||
item.put("keyholderName", u.getName());
|
||||
} else {
|
||||
item.put("lockeeName", u.getName());
|
||||
}
|
||||
if (u.getProfilePicture() != null) {
|
||||
item.put("partnerPic", u.getProfilePicture());
|
||||
}
|
||||
});
|
||||
}
|
||||
return item;
|
||||
}).toList();
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package de.oaa.xxx.games.chastity.history;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.oaa.xxx.games.chastity.LockType;
|
||||
|
||||
public record LockHistoryDTO (UUID historyId, UUID userId, LocalDateTime startTime, LocalDateTime endTime, LockType type, UUID lockedBy, String lockName, long durationMinutes, String role) {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package de.oaa.xxx.games.chastity.history;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.oaa.xxx.games.chastity.LockType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "lock_history")
|
||||
public class LockHistoryEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
@Column
|
||||
private UUID historyId;
|
||||
@Column(nullable = false)
|
||||
private UUID userId;
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime startTime;
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime endTime;
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
private LockType type;
|
||||
@Column
|
||||
private UUID lockedBy;
|
||||
|
||||
@Column
|
||||
private String lockName;
|
||||
|
||||
@Column(nullable = false, columnDefinition = "BIGINT DEFAULT 0")
|
||||
private long durationMinutes;
|
||||
|
||||
// LOCKEE oder KEYHOLDER
|
||||
@Column(nullable = false, length = 20)
|
||||
private String role;
|
||||
|
||||
public LockHistoryDTO toLockHistory() {
|
||||
return new LockHistoryDTO(historyId, userId, startTime, endTime, type, lockedBy, lockName, durationMinutes, role);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package de.oaa.xxx.games.chastity.history;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface LockHistoryRepository extends JpaRepository<LockHistoryEntity, UUID> {
|
||||
|
||||
List<LockHistoryEntity> findByUserIdOrderByEndTimeDesc(UUID userId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package de.oaa.xxx.games.history;
|
||||
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/gamehistory")
|
||||
public class GameHistoryController {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final GameHistoryRepository gameHistoryRepository;
|
||||
|
||||
public GameHistoryController(UserRepository userRepository, GameHistoryRepository gameHistoryRepository) {
|
||||
this.userRepository = userRepository;
|
||||
this.gameHistoryRepository = gameHistoryRepository;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@Transactional(readOnly = true)
|
||||
public ResponseEntity<List<Map<String, Object>>> get(@RequestParam UUID userId, Principal principal) {
|
||||
var meOpt = userRepository.findByEmail(principal.getName());
|
||||
if (meOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||
|
||||
var result = gameHistoryRepository.findByParticipantUserId(userId).stream()
|
||||
.map(e -> {
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("historyId", e.getHistoryId());
|
||||
item.put("gameType", e.getGameType());
|
||||
item.put("gameName", e.getGameName() != null ? e.getGameName() : "");
|
||||
item.put("lockName", e.getGameName() != null ? e.getGameName() : "");
|
||||
item.put("startTime", e.getStartTime().toString());
|
||||
item.put("unlockTime", e.getEndTime().toString());
|
||||
item.put("durationMinutes", e.getDurationMinutes());
|
||||
|
||||
List<Map<String, Object>> participants = e.getParticipants().stream()
|
||||
.map(p -> {
|
||||
Map<String, Object> pm = new LinkedHashMap<>();
|
||||
pm.put("userId", p.getUserId());
|
||||
pm.put("role", p.getRole());
|
||||
userRepository.findById(p.getUserId()).ifPresent(u -> {
|
||||
pm.put("name", u.getName());
|
||||
pm.put("picture", u.getProfilePicture());
|
||||
});
|
||||
return pm;
|
||||
})
|
||||
.toList();
|
||||
item.put("participants", participants);
|
||||
|
||||
// Abwärtskompatible Felder für benutzer.html (wird später angepasst)
|
||||
e.getParticipants().stream()
|
||||
.filter(p -> p.getUserId().equals(userId))
|
||||
.findFirst()
|
||||
.ifPresent(own -> item.put("role", own.getRole().name()));
|
||||
|
||||
e.getParticipants().stream()
|
||||
.filter(p -> !p.getUserId().equals(userId))
|
||||
.findFirst()
|
||||
.ifPresent(partner -> userRepository.findById(partner.getUserId()).ifPresent(u -> {
|
||||
if (GameRole.LOCKEE == partner.getRole()) {
|
||||
item.put("lockeeName", u.getName());
|
||||
} else if (GameRole.KEYHOLDER == partner.getRole()) {
|
||||
item.put("keyholderName", u.getName());
|
||||
}
|
||||
item.put("partnerPic", u.getProfilePicture());
|
||||
}));
|
||||
|
||||
return item;
|
||||
}).toList();
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.oaa.xxx.games.history;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public record GameHistoryDTO(
|
||||
UUID historyId,
|
||||
GameType gameType,
|
||||
LocalDateTime startTime,
|
||||
LocalDateTime endTime,
|
||||
String gameName,
|
||||
long durationMinutes,
|
||||
List<ParticipantDTO> participants
|
||||
) {
|
||||
public record ParticipantDTO(UUID userId, GameRole role) {}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package de.oaa.xxx.games.history;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "game_history")
|
||||
public class GameHistoryEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
@Column
|
||||
private UUID historyId;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false, length = 20)
|
||||
private GameType gameType;
|
||||
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime startTime;
|
||||
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime endTime;
|
||||
|
||||
@Column
|
||||
private String gameName;
|
||||
|
||||
@Column(nullable = false, columnDefinition = "BIGINT DEFAULT 0")
|
||||
private long durationMinutes;
|
||||
|
||||
@OneToMany(mappedBy = "history", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
|
||||
private List<GameHistoryParticipantEntity> participants = new ArrayList<>();
|
||||
|
||||
public void addParticipant(UUID userId, GameRole role) {
|
||||
GameHistoryParticipantEntity p = new GameHistoryParticipantEntity();
|
||||
p.setUserId(userId);
|
||||
p.setRole(role);
|
||||
p.setHistory(this);
|
||||
participants.add(p);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package de.oaa.xxx.games.history;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "game_history_participant")
|
||||
public class GameHistoryParticipantEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
@Column
|
||||
private UUID participantId;
|
||||
|
||||
@Column(nullable = false)
|
||||
private UUID userId;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false, length = 20)
|
||||
private GameRole role;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "history_id", nullable = false)
|
||||
private GameHistoryEntity history;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package de.oaa.xxx.games.history;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface GameHistoryParticipantRepository extends JpaRepository<GameHistoryParticipantEntity, UUID> {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.oaa.xxx.games.history;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface GameHistoryRepository extends JpaRepository<GameHistoryEntity, UUID> {
|
||||
|
||||
@Query("SELECT DISTINCT h FROM GameHistoryEntity h JOIN h.participants p WHERE p.userId = :userId ORDER BY h.endTime DESC")
|
||||
List<GameHistoryEntity> findByParticipantUserId(@Param("userId") UUID userId);
|
||||
|
||||
@Query("SELECT DISTINCT h FROM GameHistoryEntity h JOIN h.participants p WHERE p.userId = :userId AND h.gameType = :gameType ORDER BY h.endTime DESC")
|
||||
List<GameHistoryEntity> findByParticipantUserIdAndGameType(@Param("userId") UUID userId, @Param("gameType") GameType gameType);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package de.oaa.xxx.games.history;
|
||||
|
||||
public enum GameRole {
|
||||
LOCKEE,
|
||||
KEYHOLDER,
|
||||
PLAYER
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package de.oaa.xxx.games.history;
|
||||
|
||||
public enum GameType {
|
||||
CARDLOCK,
|
||||
TIMELOCK,
|
||||
BDSM,
|
||||
VANILLA
|
||||
}
|
||||
@@ -165,6 +165,56 @@ public class MailTemplateService {
|
||||
);
|
||||
}
|
||||
|
||||
public String buildNotificationMail(String name, String text, String targetUrl, String baseUrl) {
|
||||
String actionButton = targetUrl != null
|
||||
? """
|
||||
<div style="text-align:center; margin:0 0 2rem 0;">
|
||||
<a href="%s%s"
|
||||
style="display:inline-block; padding:0.75rem 2.5rem; background:%s; color:#ffffff;
|
||||
border-radius:6px; text-decoration:none; font-weight:600; font-size:1rem;">
|
||||
Zur Anwendung
|
||||
</a>
|
||||
</div>
|
||||
""".formatted(baseUrl, targetUrl, colorPrimary)
|
||||
: "<div style=\"margin:0 0 2rem 0;\"></div>";
|
||||
|
||||
String settingsUrl = baseUrl + "/einstellungen.html#sec-benachrichtigungen";
|
||||
|
||||
return """
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<body style="margin:0; padding:2rem; background:%s; font-family:'Segoe UI',Arial,sans-serif; color:%s;">
|
||||
<div style="max-width:460px; margin:0 auto; background:%s; border:1px solid %s; border-radius:12px; padding:2.5rem; box-shadow:0 8px 32px rgba(0,0,0,0.5);">
|
||||
|
||||
<h1 style="color:%s; text-align:center; margin:0 0 1.5rem 0; font-size:1.6rem;">XXX The Game</h1>
|
||||
|
||||
<p style="color:%s; margin:0 0 0.75rem 0;">Hallo %s,</p>
|
||||
<p style="color:%s; margin:0 0 2rem 0;">%s</p>
|
||||
|
||||
%s
|
||||
|
||||
<hr style="border:none; border-top:1px solid %s; margin:0 0 1.5rem 0;">
|
||||
|
||||
<p style="color:%s; font-size:0.85em; margin:0;">
|
||||
Du erhältst diese E-Mail, weil du E-Mail-Benachrichtigungen für diese Kategorie aktiviert hast.
|
||||
Du kannst deine Einstellungen jederzeit unter
|
||||
<a href="%s" style="color:%s;">Einstellungen → Benachrichtigungen</a> anpassen.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
""".formatted(
|
||||
colorBg, colorText,
|
||||
colorCard, colorSecondary,
|
||||
colorPrimary,
|
||||
colorText, name,
|
||||
colorText, text,
|
||||
actionButton,
|
||||
colorSecondary,
|
||||
colorMuted, settingsUrl, colorPrimary
|
||||
);
|
||||
}
|
||||
|
||||
public String buildActivationMail(String name, String activationLink, String activatePageUrl, String uuid) {
|
||||
return """
|
||||
<!DOCTYPE html>
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import de.oaa.xxx.user.Registration;
|
||||
import de.oaa.xxx.user.UserController;
|
||||
|
||||
|
||||
@@ -29,12 +28,7 @@ public class ActivationController {
|
||||
public ResponseEntity<Void> activate(@PathVariable String uuid) {
|
||||
RegistrationEntity registration = registrationRepository.findById(UUID.fromString(uuid)).orElse(null);
|
||||
if (registration != null && !Boolean.TRUE.equals(registration.getActivated())) {
|
||||
Registration reg = new Registration();
|
||||
reg.setEmail(registration.getEmail());
|
||||
reg.setName(registration.getName());
|
||||
reg.setPasswordHash(registration.getPassword());
|
||||
|
||||
ResponseEntity<Void> response = userController.userAnlegen(reg);
|
||||
ResponseEntity<Void> response = userController.userAnlegen(registration.toRegistration());
|
||||
if (response.getStatusCode().is2xxSuccessful()) {
|
||||
registration.setActivated(Boolean.TRUE);
|
||||
registrationRepository.save(registration);
|
||||
|
||||
@@ -3,6 +3,7 @@ package de.oaa.xxx.registration;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@@ -13,6 +14,7 @@ public class Registration {
|
||||
private String name;
|
||||
private String email;
|
||||
private String passwordHash;
|
||||
private LocalDate geburtsdatum;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -14,6 +14,9 @@ import de.oaa.xxx.mail.MailService;
|
||||
import de.oaa.xxx.mail.MailTemplateService;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Period;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/registration")
|
||||
public class RegistrationController {
|
||||
@@ -39,6 +42,11 @@ public class RegistrationController {
|
||||
@PostMapping
|
||||
public ResponseEntity<String> create(@RequestBody Registration registration) {
|
||||
LOGGER.info("POST {}: {}", getClass().getName(), registration);
|
||||
if (registration.getGeburtsdatum() == null
|
||||
|| Period.between(registration.getGeburtsdatum(), LocalDate.now()).getYears() < 18) {
|
||||
LOGGER.warn("Registrierung abgelehnt – Mindestalter nicht erreicht");
|
||||
return ResponseEntity.status(422).build();
|
||||
}
|
||||
if (registrationRepository.findByEmail(registration.getEmail()).isPresent()
|
||||
|| userRepository.findByEmail(registration.getEmail()).isPresent()) {
|
||||
LOGGER.warn("User mit E-Mail {} bereits vorhanden", registration.getEmail());
|
||||
|
||||
@@ -7,6 +7,7 @@ import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@@ -26,6 +27,8 @@ public class RegistrationEntity {
|
||||
private String password;
|
||||
@Column
|
||||
private Boolean activated;
|
||||
@Column
|
||||
private LocalDate geburtsdatum;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
@@ -38,6 +41,7 @@ public class RegistrationEntity {
|
||||
registration.setEmail(email);
|
||||
registration.setName(name);
|
||||
registration.setPasswordHash(password);
|
||||
registration.setGeburtsdatum(geburtsdatum);
|
||||
return registration;
|
||||
}
|
||||
|
||||
@@ -48,6 +52,7 @@ public class RegistrationEntity {
|
||||
entity.setActivated(Boolean.FALSE);
|
||||
entity.setName(registration.getName());
|
||||
entity.setPassword(registration.getPasswordHash());
|
||||
entity.setGeburtsdatum(registration.getGeburtsdatum());
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package de.oaa.xxx.session.repository;
|
||||
|
||||
import de.oaa.xxx.session.entity.SessionEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface SessionRepository extends JpaRepository<SessionEntity, UUID> {
|
||||
|
||||
Optional<SessionEntity> findByUserId(UUID userId);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import de.oaa.xxx.social.dto.MessageDto;
|
||||
import de.oaa.xxx.social.dto.UserProfile;
|
||||
import de.oaa.xxx.social.entity.FriendshipEntity;
|
||||
import de.oaa.xxx.social.entity.FriendshipEntity.Status;
|
||||
import de.oaa.xxx.social.entity.MessageCause;
|
||||
import de.oaa.xxx.social.entity.MessageEntity;
|
||||
import de.oaa.xxx.social.repository.FriendshipRepository;
|
||||
import de.oaa.xxx.social.repository.MessageRepository;
|
||||
@@ -31,15 +32,18 @@ public class SocialController {
|
||||
private final FriendshipRepository friendshipRepository;
|
||||
private final MessageRepository messageRepository;
|
||||
private final SseService sseService;
|
||||
private final SystemMessageService systemMessageService;
|
||||
|
||||
public SocialController(UserRepository userRepository,
|
||||
FriendshipRepository friendshipRepository,
|
||||
MessageRepository messageRepository,
|
||||
SseService sseService) {
|
||||
SseService sseService,
|
||||
SystemMessageService systemMessageService) {
|
||||
this.userRepository = userRepository;
|
||||
this.friendshipRepository = friendshipRepository;
|
||||
this.messageRepository = messageRepository;
|
||||
this.sseService = sseService;
|
||||
this.systemMessageService = systemMessageService;
|
||||
}
|
||||
|
||||
record FriendRequestBody(UUID receiverId) {}
|
||||
@@ -94,6 +98,13 @@ public class SocialController {
|
||||
f.setCreatedAt(LocalDateTime.now());
|
||||
friendshipRepository.save(f);
|
||||
LOGGER.info("User {} hat Freundschaftsanfrage an User {} gesendet", myId, body.receiverId());
|
||||
|
||||
String senderName = meOpt.get().getName();
|
||||
systemMessageService.send(myId, body.receiverId(),
|
||||
senderName + " hat dir eine Freundschaftsanfrage gesendet.",
|
||||
"/benutzer.html?userId=" + myId,
|
||||
MessageCause.FRIENDREQUEST);
|
||||
|
||||
return ResponseEntity.status(201).build();
|
||||
}
|
||||
|
||||
@@ -299,22 +310,52 @@ public class SocialController {
|
||||
// ── Helpers ──
|
||||
|
||||
private UserProfile toUserProfileWithStatus(UserEntity user, UUID myId) {
|
||||
boolean isOwn = user.getUserId().equals(myId);
|
||||
String status = "NONE";
|
||||
var existing = friendshipRepository.findExisting(myId, user.getUserId());
|
||||
if (existing.isPresent()) {
|
||||
FriendshipEntity f = existing.get();
|
||||
if (f.getStatus() == Status.ACCEPTED) {
|
||||
status = "FRIEND";
|
||||
} else if (f.getSenderId().equals(myId)) {
|
||||
status = "PENDING_SENT";
|
||||
} else {
|
||||
status = "PENDING_RECEIVED";
|
||||
if (!isOwn) {
|
||||
var existing = friendshipRepository.findExisting(myId, user.getUserId());
|
||||
if (existing.isPresent()) {
|
||||
FriendshipEntity f = existing.get();
|
||||
if (f.getStatus() == Status.ACCEPTED) {
|
||||
status = "FRIEND";
|
||||
} else if (f.getSenderId().equals(myId)) {
|
||||
status = "PENDING_SENT";
|
||||
} else {
|
||||
status = "PENDING_RECEIVED";
|
||||
}
|
||||
}
|
||||
}
|
||||
return new UserProfile(user.getUserId(), user.getName(), user.getProfilePicture(), user.getProfilePictureHq(),
|
||||
status, user.getAlter(), user.getGroesse(), user.getGewicht(),
|
||||
user.getGeschlecht(), user.getNeigung(), user.getBeziehungsstatus(), user.getBeschreibung(),
|
||||
user.getLockeeXp(), user.getKeyholderXp());
|
||||
boolean isFriend = isOwn || "FRIEND".equals(status);
|
||||
|
||||
// Grunddaten nur zurückgeben wenn berechtigt
|
||||
de.oaa.xxx.user.Sichtbarkeit svGd = user.getSichtbarkeitGrunddaten();
|
||||
boolean showGrunddaten = isOwn || svGd == de.oaa.xxx.user.Sichtbarkeit.ALLE
|
||||
|| (svGd == de.oaa.xxx.user.Sichtbarkeit.NUR_FREUNDE && isFriend);
|
||||
|
||||
// XP nur zurückgeben wenn berechtigt
|
||||
de.oaa.xxx.user.Sichtbarkeit svXp = user.getSichtbarkeitXp();
|
||||
boolean showXp = isOwn || svXp == de.oaa.xxx.user.Sichtbarkeit.ALLE
|
||||
|| (svXp == de.oaa.xxx.user.Sichtbarkeit.NUR_FREUNDE && isFriend);
|
||||
|
||||
return new UserProfile(
|
||||
user.getUserId(), user.getName(), user.getProfilePicture(), user.getProfilePictureHq(),
|
||||
status,
|
||||
showGrunddaten ? user.getAlter() : null,
|
||||
showGrunddaten ? user.getGroesse() : null,
|
||||
showGrunddaten ? user.getGewicht() : null,
|
||||
showGrunddaten ? user.getGeschlecht() : null,
|
||||
showGrunddaten ? user.getNeigung() : null,
|
||||
showGrunddaten ? user.getBeziehungsstatus() : null,
|
||||
showGrunddaten ? user.getBeschreibung() : null,
|
||||
showXp ? user.getLockeeXp() : 0,
|
||||
showXp ? user.getKeyholderXp() : 0,
|
||||
user.getSichtbarkeitGrunddaten(),
|
||||
user.getSichtbarkeitGalerie(),
|
||||
user.getSichtbarkeitFreunde(),
|
||||
user.getSichtbarkeitFeed(),
|
||||
user.getSichtbarkeitPinnwand(),
|
||||
user.getSichtbarkeitXp(),
|
||||
user.getSichtbarkeitLockhistorie());
|
||||
}
|
||||
|
||||
private MessageDto toMessageDto(MessageEntity m) {
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package de.oaa.xxx.social;
|
||||
|
||||
import de.oaa.xxx.mail.Email;
|
||||
import de.oaa.xxx.mail.MailService;
|
||||
import de.oaa.xxx.mail.MailTemplateService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import de.oaa.xxx.social.entity.MessageCause;
|
||||
import de.oaa.xxx.social.entity.MessageEntity;
|
||||
import de.oaa.xxx.social.entity.NotificationPreferenceEntity;
|
||||
import de.oaa.xxx.social.repository.MessageRepository;
|
||||
import de.oaa.xxx.social.repository.NotificationPreferenceRepository;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class SystemMessageService {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SystemMessageService.class);
|
||||
|
||||
private final MessageRepository messageRepository;
|
||||
private final NotificationPreferenceRepository preferenceRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final SseService sseService;
|
||||
private final MailService mailService;
|
||||
private final MailTemplateService mailTemplateService;
|
||||
|
||||
@Value("${app.base-url:http://localhost:8080}")
|
||||
private String baseUrl;
|
||||
|
||||
public SystemMessageService(MessageRepository messageRepository,
|
||||
NotificationPreferenceRepository preferenceRepository,
|
||||
UserRepository userRepository,
|
||||
SseService sseService,
|
||||
MailService mailService,
|
||||
MailTemplateService mailTemplateService) {
|
||||
this.messageRepository = messageRepository;
|
||||
this.preferenceRepository = preferenceRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.sseService = sseService;
|
||||
this.mailService = mailService;
|
||||
this.mailTemplateService = mailTemplateService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sendet eine Systemnachricht unter Berücksichtigung der Benachrichtigungseinstellungen des Empfängers.
|
||||
*/
|
||||
public void send(UUID senderId, UUID receiverId, String text, String targetUrl, MessageCause cause) {
|
||||
if (senderId == null || receiverId == null) return;
|
||||
|
||||
NotificationPreferenceEntity pref = preferenceRepository
|
||||
.findByUserIdAndCause(receiverId, cause)
|
||||
.orElseGet(() -> NotificationPreferenceEntity.defaultFor(receiverId, cause));
|
||||
|
||||
// FRIENDREQUEST ist immer in-app, unabhängig von der Einstellung
|
||||
boolean sendInApp = cause == MessageCause.FRIENDREQUEST || pref.isInApp();
|
||||
|
||||
if (sendInApp) {
|
||||
MessageEntity msg = new MessageEntity();
|
||||
msg.setMessageId(UUID.randomUUID());
|
||||
msg.setSenderId(senderId);
|
||||
msg.setReceiverId(receiverId);
|
||||
msg.setText(text);
|
||||
msg.setSentAt(LocalDateTime.now());
|
||||
msg.setSystemMessage(true);
|
||||
msg.setMessageCause(cause);
|
||||
if (targetUrl != null) msg.setTargetUrl(targetUrl);
|
||||
messageRepository.save(msg);
|
||||
|
||||
long unread = messageRepository.countByReceiverIdAndSystemMessageAndReadAtIsNull(receiverId, true);
|
||||
sseService.push(receiverId, "NOTIFICATION", Map.of("unreadCount", unread, "text", text));
|
||||
}
|
||||
|
||||
if (pref.isEmail()) {
|
||||
userRepository.findById(receiverId).ifPresent(user -> {
|
||||
try {
|
||||
Email email = new Email();
|
||||
email.setEmailAdresse(user.getEmail());
|
||||
email.setTitel(causeTitel(cause));
|
||||
email.setText(mailTemplateService.buildNotificationMail(user.getName(), text, targetUrl, baseUrl));
|
||||
mailService.send(email);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("E-Mail-Benachrichtigung fehlgeschlagen für userId={}: {}", receiverId, e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private String causeTitel(MessageCause cause) {
|
||||
return switch (cause) {
|
||||
case INVITATION -> "XXX The Game – Neue Einladung";
|
||||
case GAME_STATE -> "XXX The Game – Spielstatus-Änderung";
|
||||
case EMERGENCY -> "XXX The Game – ⚠️ Notfall";
|
||||
case FRIENDREQUEST -> "XXX The Game – Neue Freundschaftsanfrage";
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package de.oaa.xxx.social.dto;
|
||||
import de.oaa.xxx.user.Beziehungsstatus;
|
||||
import de.oaa.xxx.user.Geschlecht;
|
||||
import de.oaa.xxx.user.Neigung;
|
||||
import de.oaa.xxx.user.Sichtbarkeit;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -20,10 +21,20 @@ public record UserProfile(
|
||||
Beziehungsstatus beziehungsstatus,
|
||||
String beschreibung,
|
||||
int lockeeXp,
|
||||
int keyholderXp
|
||||
int keyholderXp,
|
||||
// Datenschutz-Einstellungen
|
||||
Sichtbarkeit sichtbarkeitGrunddaten,
|
||||
Sichtbarkeit sichtbarkeitGalerie,
|
||||
Sichtbarkeit sichtbarkeitFreunde,
|
||||
Sichtbarkeit sichtbarkeitFeed,
|
||||
Sichtbarkeit sichtbarkeitPinnwand,
|
||||
Sichtbarkeit sichtbarkeitXp,
|
||||
Sichtbarkeit sichtbarkeitLockhistorie
|
||||
) {
|
||||
/** Compact constructor for contexts where profile details are not needed (friend list etc.) */
|
||||
public UserProfile(UUID userId, String name, String profilePicture, String profilePictureHq, String friendStatus) {
|
||||
this(userId, name, profilePicture, profilePictureHq, friendStatus, null, null, null, null, null, null, null, 0, 0);
|
||||
this(userId, name, profilePicture, profilePictureHq, friendStatus,
|
||||
null, null, null, null, null, null, null, 0, 0,
|
||||
null, null, null, null, null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package de.oaa.xxx.social.entity;
|
||||
|
||||
public enum MessageCause {
|
||||
INVITATION,
|
||||
GAME_STATE,
|
||||
EMERGENCY,
|
||||
FRIENDREQUEST
|
||||
}
|
||||
@@ -35,6 +35,10 @@ public class MessageEntity {
|
||||
@Column(nullable = false)
|
||||
private boolean systemMessage = false;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(length = 20)
|
||||
private MessageCause messageCause;
|
||||
|
||||
@Column(length = 500)
|
||||
private String targetUrl;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package de.oaa.xxx.social.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "notification_preference", uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "cause"}))
|
||||
public class NotificationPreferenceEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private UUID id;
|
||||
|
||||
@Column(name = "user_id", nullable = false)
|
||||
private UUID userId;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false, length = 20)
|
||||
private MessageCause cause;
|
||||
|
||||
@Column(nullable = false)
|
||||
private boolean inApp = true;
|
||||
|
||||
@Column(nullable = false)
|
||||
private boolean email = false;
|
||||
|
||||
/** Erzeugt eine nicht persistierte Standardpräferenz für unbekannte/neue Causes. */
|
||||
public static NotificationPreferenceEntity defaultFor(UUID userId, MessageCause cause) {
|
||||
NotificationPreferenceEntity p = new NotificationPreferenceEntity();
|
||||
p.setUserId(userId);
|
||||
p.setCause(cause);
|
||||
// inApp=true und email=false sind bereits die Java-Felddefaults
|
||||
return p;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package de.oaa.xxx.social.repository;
|
||||
|
||||
import de.oaa.xxx.social.entity.MessageCause;
|
||||
import de.oaa.xxx.social.entity.NotificationPreferenceEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface NotificationPreferenceRepository extends JpaRepository<NotificationPreferenceEntity, UUID> {
|
||||
|
||||
List<NotificationPreferenceEntity> findByUserId(UUID userId);
|
||||
|
||||
Optional<NotificationPreferenceEntity> findByUserIdAndCause(UUID userId, MessageCause cause);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package de.oaa.xxx.user;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class Registration {
|
||||
|
||||
private UUID id;
|
||||
private String name;
|
||||
private String email;
|
||||
private String passwordHash;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Registration [id=" + id + ", name=" + name + ", email=" + email + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package de.oaa.xxx.user;
|
||||
|
||||
public enum Sichtbarkeit {
|
||||
ALLE,
|
||||
NUR_FREUNDE,
|
||||
NUR_ICH
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package de.oaa.xxx.user;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Period;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@@ -14,7 +16,7 @@ public class User {
|
||||
private String email;
|
||||
private String password;
|
||||
private String profilePicture;
|
||||
private Integer alter;
|
||||
private LocalDate geburtsdatum;
|
||||
private Integer groesse;
|
||||
private Integer gewicht;
|
||||
private Geschlecht geschlecht;
|
||||
@@ -22,6 +24,10 @@ public class User {
|
||||
private Beziehungsstatus beziehungsstatus;
|
||||
private String beschreibung;
|
||||
|
||||
public Integer getAlter() {
|
||||
return geburtsdatum != null ? Period.between(geburtsdatum, LocalDate.now()).getYears() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User[userId=" + userId + ", name=" + name + ", email=" + email + "]";
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package de.oaa.xxx.user;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Period;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -10,6 +15,7 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -17,6 +23,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import de.oaa.xxx.aufgaben.repository.AufgabeRepository;
|
||||
import de.oaa.xxx.games.bdsm.entity.BdsmDefaultsEntity;
|
||||
import de.oaa.xxx.games.bdsm.repository.BdsmDefaultsRepository;
|
||||
import de.oaa.xxx.aufgaben.repository.AufgabenGruppeRepository;
|
||||
import de.oaa.xxx.aufgaben.repository.FavoritRepository;
|
||||
import de.oaa.xxx.aufgaben.repository.GruppenAboRepository;
|
||||
@@ -24,19 +32,23 @@ import de.oaa.xxx.aufgaben.repository.SperreRepository;
|
||||
import de.oaa.xxx.aufgaben.repository.StrafeRepository;
|
||||
import de.oaa.xxx.aufgaben.repository.ToyRepository;
|
||||
import de.oaa.xxx.emailchange.EmailChangeRepository;
|
||||
import de.oaa.xxx.games.bdsm.entity.AktiveSperreEntity;
|
||||
import de.oaa.xxx.games.bdsm.entity.MitspielerEntity;
|
||||
import de.oaa.xxx.games.bdsm.repository.AktiveSperreRepository;
|
||||
import de.oaa.xxx.games.bdsm.repository.BdsmGameRepository;
|
||||
import de.oaa.xxx.games.bdsm.repository.MitspielerRepository;
|
||||
import de.oaa.xxx.passwordreset.PasswordResetRepository;
|
||||
import de.oaa.xxx.registration.Registration;
|
||||
import de.oaa.xxx.registration.RegistrationRepository;
|
||||
import de.oaa.xxx.session.entity.AktiveSperreEntity;
|
||||
import de.oaa.xxx.session.entity.MitspielerEntity;
|
||||
import de.oaa.xxx.session.repository.AktiveSperreRepository;
|
||||
import de.oaa.xxx.session.repository.MitspielerRepository;
|
||||
import de.oaa.xxx.session.repository.SessionRepository;
|
||||
import de.oaa.xxx.social.repository.ProfileImageLikeRepository;
|
||||
import de.oaa.xxx.social.repository.ProfileImageRepository;
|
||||
import de.oaa.xxx.social.entity.MessageCause;
|
||||
import de.oaa.xxx.social.entity.NotificationPreferenceEntity;
|
||||
import de.oaa.xxx.social.repository.KommentarLikeRepository;
|
||||
import de.oaa.xxx.social.repository.KommentarRepository;
|
||||
import de.oaa.xxx.social.repository.NotificationPreferenceRepository;
|
||||
import de.oaa.xxx.social.repository.PinnwandEintragRepository;
|
||||
import de.oaa.xxx.social.repository.PinnwandLikeRepository;
|
||||
import de.oaa.xxx.social.repository.KommentarRepository;
|
||||
import de.oaa.xxx.social.repository.KommentarLikeRepository;
|
||||
import de.oaa.xxx.social.repository.ProfileImageLikeRepository;
|
||||
import de.oaa.xxx.social.repository.ProfileImageRepository;
|
||||
import jakarta.transaction.Transactional;
|
||||
|
||||
@RestController
|
||||
@@ -54,7 +66,7 @@ public class UserController {
|
||||
private final ToyRepository toyRepository;
|
||||
private final FavoritRepository favoritRepository;
|
||||
private final GruppenAboRepository gruppenAboRepository;
|
||||
private final SessionRepository sessionRepository;
|
||||
private final BdsmGameRepository sessionRepository;
|
||||
private final AktiveSperreRepository aktiveSperreRepository;
|
||||
private final MitspielerRepository mitspielerRepository;
|
||||
private final EmailChangeRepository emailChangeRepository;
|
||||
@@ -65,6 +77,8 @@ public class UserController {
|
||||
private final PinnwandLikeRepository pinnwandLikeRepository;
|
||||
private final KommentarRepository kommentarRepository;
|
||||
private final KommentarLikeRepository kommentarLikeRepository;
|
||||
private final NotificationPreferenceRepository notificationPreferenceRepository;
|
||||
private final BdsmDefaultsRepository bdsmDefaultsRepository;
|
||||
|
||||
public UserController(UserRepository userRepository,
|
||||
RegistrationRepository registrationRepository,
|
||||
@@ -75,7 +89,7 @@ public class UserController {
|
||||
ToyRepository toyRepository,
|
||||
FavoritRepository favoritRepository,
|
||||
GruppenAboRepository gruppenAboRepository,
|
||||
SessionRepository sessionRepository,
|
||||
BdsmGameRepository sessionRepository,
|
||||
AktiveSperreRepository aktiveSperreRepository,
|
||||
MitspielerRepository mitspielerRepository,
|
||||
EmailChangeRepository emailChangeRepository,
|
||||
@@ -85,7 +99,9 @@ public class UserController {
|
||||
PinnwandEintragRepository pinnwandEintragRepository,
|
||||
PinnwandLikeRepository pinnwandLikeRepository,
|
||||
KommentarRepository kommentarRepository,
|
||||
KommentarLikeRepository kommentarLikeRepository) {
|
||||
KommentarLikeRepository kommentarLikeRepository,
|
||||
NotificationPreferenceRepository notificationPreferenceRepository,
|
||||
BdsmDefaultsRepository bdsmDefaultsRepository) {
|
||||
this.userRepository = userRepository;
|
||||
this.registrationRepository = registrationRepository;
|
||||
this.aufgabenGruppeRepository = aufgabenGruppeRepository;
|
||||
@@ -106,12 +122,23 @@ public class UserController {
|
||||
this.pinnwandLikeRepository = pinnwandLikeRepository;
|
||||
this.kommentarRepository = kommentarRepository;
|
||||
this.kommentarLikeRepository = kommentarLikeRepository;
|
||||
this.notificationPreferenceRepository = notificationPreferenceRepository;
|
||||
this.bdsmDefaultsRepository = bdsmDefaultsRepository;
|
||||
}
|
||||
|
||||
record ProfilePictureRequest(String picture, String pictureHq) {}
|
||||
record NameChangeRequest(String name) {}
|
||||
record ProfileRequest(Integer alter, Integer groesse, Integer gewicht,
|
||||
record GeburtsdatumChangeRequest(LocalDate geburtsdatum) {}
|
||||
record ProfileRequest(Integer groesse, Integer gewicht,
|
||||
Geschlecht geschlecht, Neigung neigung, Beziehungsstatus beziehungsstatus, String beschreibung) {}
|
||||
record PrivacyRequest(
|
||||
Sichtbarkeit sichtbarkeitGrunddaten,
|
||||
Sichtbarkeit sichtbarkeitGalerie,
|
||||
Sichtbarkeit sichtbarkeitFreunde,
|
||||
Sichtbarkeit sichtbarkeitFeed,
|
||||
Sichtbarkeit sichtbarkeitPinnwand,
|
||||
Sichtbarkeit sichtbarkeitXp,
|
||||
Sichtbarkeit sichtbarkeitLockhistorie) {}
|
||||
|
||||
@PutMapping("/me/picture")
|
||||
public ResponseEntity<Void> updateProfilePicture(@RequestBody ProfilePictureRequest request, Principal principal) {
|
||||
@@ -132,7 +159,6 @@ public class UserController {
|
||||
if (request.beschreibung() != null && request.beschreibung().length() > 600) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
user.setAlter(request.alter());
|
||||
user.setGroesse(request.groesse());
|
||||
user.setGewicht(request.gewicht());
|
||||
user.setGeschlecht(request.geschlecht());
|
||||
@@ -144,6 +170,126 @@ public class UserController {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PutMapping("/me/privacy")
|
||||
public ResponseEntity<Void> updatePrivacy(@RequestBody PrivacyRequest request, Principal principal) {
|
||||
var userOpt = userRepository.findByEmail(principal.getName());
|
||||
if (userOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||
var user = userOpt.get();
|
||||
if (request.sichtbarkeitGrunddaten() != null) user.setSichtbarkeitGrunddaten(request.sichtbarkeitGrunddaten());
|
||||
if (request.sichtbarkeitGalerie() != null) user.setSichtbarkeitGalerie(request.sichtbarkeitGalerie());
|
||||
if (request.sichtbarkeitFreunde() != null) user.setSichtbarkeitFreunde(request.sichtbarkeitFreunde());
|
||||
if (request.sichtbarkeitFeed() != null) user.setSichtbarkeitFeed(request.sichtbarkeitFeed());
|
||||
if (request.sichtbarkeitPinnwand() != null) user.setSichtbarkeitPinnwand(request.sichtbarkeitPinnwand());
|
||||
if (request.sichtbarkeitXp() != null) user.setSichtbarkeitXp(request.sichtbarkeitXp());
|
||||
if (request.sichtbarkeitLockhistorie()!= null) user.setSichtbarkeitLockhistorie(request.sichtbarkeitLockhistorie());
|
||||
userRepository.save(user);
|
||||
LOGGER.info("User {} hat Datenschutz-Einstellungen aktualisiert", user.getUserId());
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
record NotificationPreferenceRequest(boolean inApp, boolean email) {}
|
||||
|
||||
@GetMapping("/me/notifications")
|
||||
public ResponseEntity<Map<String, Object>> getNotifications(Principal principal) {
|
||||
var userOpt = userRepository.findByEmail(principal.getName());
|
||||
if (userOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||
UUID userId = userOpt.get().getUserId();
|
||||
|
||||
Map<String, NotificationPreferenceEntity> byKey = notificationPreferenceRepository.findByUserId(userId)
|
||||
.stream().collect(Collectors.toMap(p -> p.getCause().name(), p -> p));
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
for (MessageCause cause : MessageCause.values()) {
|
||||
NotificationPreferenceEntity pref = byKey.getOrDefault(
|
||||
cause.name(), NotificationPreferenceEntity.defaultFor(userId, cause));
|
||||
Map<String, Object> entry = new LinkedHashMap<>();
|
||||
entry.put("inApp", pref.isInApp());
|
||||
entry.put("email", pref.isEmail());
|
||||
result.put(cause.name(), entry);
|
||||
}
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
@PutMapping("/me/notifications")
|
||||
public ResponseEntity<Void> updateNotifications(@RequestBody Map<String, NotificationPreferenceRequest> request, Principal principal) {
|
||||
var userOpt = userRepository.findByEmail(principal.getName());
|
||||
if (userOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||
UUID userId = userOpt.get().getUserId();
|
||||
|
||||
for (var entry : request.entrySet()) {
|
||||
MessageCause cause;
|
||||
try {
|
||||
cause = MessageCause.valueOf(entry.getKey());
|
||||
} catch (IllegalArgumentException e) {
|
||||
continue;
|
||||
}
|
||||
NotificationPreferenceEntity pref = notificationPreferenceRepository
|
||||
.findByUserIdAndCause(userId, cause)
|
||||
.orElseGet(() -> {
|
||||
NotificationPreferenceEntity n = new NotificationPreferenceEntity();
|
||||
n.setUserId(userId);
|
||||
n.setCause(cause);
|
||||
return n;
|
||||
});
|
||||
pref.setInApp(entry.getValue().inApp());
|
||||
pref.setEmail(entry.getValue().email());
|
||||
notificationPreferenceRepository.save(pref);
|
||||
}
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
record BdsmDefaultsRequest(List<String> spieltMit, List<String> rollen, List<String> werkzeuge) {}
|
||||
|
||||
@GetMapping("/me/bdsm-defaults")
|
||||
public ResponseEntity<Map<String, Object>> getBdsmDefaults(Principal principal) {
|
||||
var userOpt = userRepository.findByEmail(principal.getName());
|
||||
if (userOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||
UUID userId = userOpt.get().getUserId();
|
||||
|
||||
BdsmDefaultsEntity d = bdsmDefaultsRepository.findByUserId(userId)
|
||||
.orElse(new BdsmDefaultsEntity());
|
||||
Map<String, Object> result = new java.util.LinkedHashMap<>();
|
||||
result.put("spieltMit", splitOrEmpty(d.getSpieltMit()));
|
||||
result.put("rollen", splitOrEmpty(d.getRollen()));
|
||||
result.put("werkzeuge", splitOrEmpty(d.getWerkzeuge()));
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
@PutMapping("/me/bdsm-defaults")
|
||||
public ResponseEntity<Void> updateBdsmDefaults(@RequestBody BdsmDefaultsRequest request, Principal principal) {
|
||||
var userOpt = userRepository.findByEmail(principal.getName());
|
||||
if (userOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||
UUID userId = userOpt.get().getUserId();
|
||||
|
||||
BdsmDefaultsEntity d = bdsmDefaultsRepository.findByUserId(userId)
|
||||
.orElseGet(() -> { BdsmDefaultsEntity n = new BdsmDefaultsEntity(); n.setUserId(userId); return n; });
|
||||
d.setSpieltMit(request.spieltMit() == null ? "" : String.join(",", request.spieltMit()));
|
||||
d.setRollen(request.rollen() == null ? "" : String.join(",", request.rollen()));
|
||||
d.setWerkzeuge(request.werkzeuge() == null ? "" : String.join(",", request.werkzeuge()));
|
||||
bdsmDefaultsRepository.save(d);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
private static List<String> splitOrEmpty(String s) {
|
||||
if (s == null || s.isBlank()) return List.of();
|
||||
return List.of(s.split(","));
|
||||
}
|
||||
|
||||
@PutMapping("/me/geburtsdatum")
|
||||
public ResponseEntity<Void> updateGeburtsdatum(@RequestBody GeburtsdatumChangeRequest request, Principal principal) {
|
||||
if (request.geburtsdatum() == null
|
||||
|| Period.between(request.geburtsdatum(), LocalDate.now()).getYears() < 18) {
|
||||
return ResponseEntity.status(422).build();
|
||||
}
|
||||
var userOpt = userRepository.findByEmail(principal.getName());
|
||||
if (userOpt.isEmpty()) return ResponseEntity.status(401).build();
|
||||
var user = userOpt.get();
|
||||
user.setGeburtsdatum(request.geburtsdatum());
|
||||
userRepository.save(user);
|
||||
LOGGER.info("User {} hat Geburtsdatum aktualisiert", user.getUserId());
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PutMapping("/me/name")
|
||||
public ResponseEntity<Void> updateName(@RequestBody NameChangeRequest request, Principal principal) {
|
||||
String newName = request.name();
|
||||
@@ -264,7 +410,14 @@ public class UserController {
|
||||
entity.setEmail(registration.getEmail());
|
||||
entity.setName(registration.getName());
|
||||
entity.setPassword(registration.getPasswordHash());
|
||||
entity.setGeburtsdatum(registration.getGeburtsdatum());
|
||||
userRepository.save(entity);
|
||||
|
||||
for (MessageCause cause : MessageCause.values()) {
|
||||
notificationPreferenceRepository.save(
|
||||
NotificationPreferenceEntity.defaultFor(entity.getUserId(), cause));
|
||||
}
|
||||
|
||||
return ResponseEntity.status(201).build();
|
||||
} catch (Exception exception) {
|
||||
LOGGER.error(exception.getMessage(), exception);
|
||||
|
||||
@@ -4,6 +4,8 @@ import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Period;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@@ -28,8 +30,8 @@ public class UserEntity {
|
||||
@Column(columnDefinition = "MEDIUMTEXT")
|
||||
private String profilePictureHq;
|
||||
|
||||
@Column(name = "benutzer_alter")
|
||||
private Integer alter;
|
||||
@Column
|
||||
private LocalDate geburtsdatum;
|
||||
|
||||
@Column
|
||||
private Integer groesse;
|
||||
@@ -58,6 +60,39 @@ public class UserEntity {
|
||||
@Column(nullable = false, columnDefinition = "INT DEFAULT 0")
|
||||
private int keyholderXp;
|
||||
|
||||
// ── Datenschutz / Sichtbarkeit ──
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'")
|
||||
private Sichtbarkeit sichtbarkeitGrunddaten = Sichtbarkeit.ALLE;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'")
|
||||
private Sichtbarkeit sichtbarkeitGalerie = Sichtbarkeit.ALLE;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'")
|
||||
private Sichtbarkeit sichtbarkeitFreunde = Sichtbarkeit.ALLE;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'")
|
||||
private Sichtbarkeit sichtbarkeitFeed = Sichtbarkeit.ALLE;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'")
|
||||
private Sichtbarkeit sichtbarkeitPinnwand = Sichtbarkeit.ALLE;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'")
|
||||
private Sichtbarkeit sichtbarkeitXp = Sichtbarkeit.ALLE;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'")
|
||||
private Sichtbarkeit sichtbarkeitLockhistorie = Sichtbarkeit.ALLE;
|
||||
|
||||
public Integer getAlter() {
|
||||
return geburtsdatum != null ? Period.between(geburtsdatum, LocalDate.now()).getYears() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UserEntity[userId=" + userId + ", name=" + name + ", email=" + email + "]";
|
||||
@@ -69,7 +104,7 @@ public class UserEntity {
|
||||
user.setName(name);
|
||||
user.setUserId(userId);
|
||||
user.setProfilePicture(profilePicture);
|
||||
user.setAlter(alter);
|
||||
user.setGeburtsdatum(geburtsdatum);
|
||||
user.setGroesse(groesse);
|
||||
user.setGewicht(gewicht);
|
||||
user.setGeschlecht(geschlecht);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Aufgaben – XXX The Game</title>
|
||||
@@ -608,44 +608,47 @@
|
||||
resetSelection();
|
||||
document.getElementById('userLoading').style.display = 'block';
|
||||
fetch(`/gruppe/list/user?page=${userPage}&size=${PAGE_SIZE}`)
|
||||
.then(r => r.json())
|
||||
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
|
||||
.then(data => {
|
||||
console.log('[aufgaben] user gruppen:', data);
|
||||
userTotalPages = data.totalPages || 1;
|
||||
renderGruppen('userList', data.content, 'user');
|
||||
try { renderGruppen('userList', data.content, 'user'); } catch(e) { console.error('[aufgaben] renderGruppen user Fehler:', e); throw e; }
|
||||
updatePaging('userPaging', 'userPrev', 'userNext', 'userPageInfo', userPage, userTotalPages);
|
||||
document.getElementById('userLoading').style.display = 'none';
|
||||
reapplyPendingExpand();
|
||||
})
|
||||
.catch(() => { document.getElementById('userLoading').textContent = 'Fehler beim Laden.'; });
|
||||
.catch(err => { console.error('[aufgaben] Fehler user gruppen:', err); document.getElementById('userLoading').textContent = 'Fehler beim Laden: ' + err.message; });
|
||||
}
|
||||
|
||||
function loadSystemGruppen() {
|
||||
resetSelection();
|
||||
document.getElementById('systemLoading').style.display = 'block';
|
||||
fetch(`/gruppe/list/system?page=${systemPage}&size=${PAGE_SIZE}`)
|
||||
.then(r => r.json())
|
||||
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
|
||||
.then(data => {
|
||||
console.log('[aufgaben] system gruppen:', data);
|
||||
systemTotalPages = data.totalPages || 1;
|
||||
renderGruppen('systemList', data.content, 'system');
|
||||
try { renderGruppen('systemList', data.content, 'system'); } catch(e) { console.error('[aufgaben] renderGruppen system Fehler:', e); throw e; }
|
||||
updatePaging('systemPaging', 'systemPrev', 'systemNext', 'systemPageInfo', systemPage, systemTotalPages);
|
||||
document.getElementById('systemLoading').style.display = 'none';
|
||||
reapplyPendingExpand();
|
||||
})
|
||||
.catch(() => { document.getElementById('systemLoading').textContent = 'Fehler beim Laden.'; });
|
||||
.catch(err => { console.error('[aufgaben] Fehler system gruppen:', err); document.getElementById('systemLoading').textContent = 'Fehler beim Laden: ' + err.message; });
|
||||
}
|
||||
|
||||
function loadAboGruppen() {
|
||||
document.getElementById('aboLoading').style.display = 'block';
|
||||
fetch(`/abo/list?page=${aboPage}&size=${PAGE_SIZE}`)
|
||||
.then(r => r.json())
|
||||
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
|
||||
.then(data => {
|
||||
console.log('[aufgaben] abo gruppen:', data);
|
||||
aboTotalPages = data.totalPages || 1;
|
||||
renderGruppen('aboList', data.content, 'abo');
|
||||
updatePaging('aboPaging', 'aboPrev', 'aboNext', 'aboPageInfo', aboPage, aboTotalPages);
|
||||
document.getElementById('aboLoading').style.display = 'none';
|
||||
reapplyPendingExpand();
|
||||
})
|
||||
.catch(() => { document.getElementById('aboLoading').textContent = 'Fehler beim Laden.'; });
|
||||
.catch(err => { console.error('[aufgaben] Fehler abo gruppen:', err); document.getElementById('aboLoading').textContent = 'Fehler beim Laden: ' + err.message; });
|
||||
}
|
||||
|
||||
function reapplyPendingExpand() {
|
||||
|
||||
128
xxxthegame/src/main/resources/static/bdsm-einladung.html
Normal file
128
xxxthegame/src/main/resources/static/bdsm-einladung.html
Normal file
@@ -0,0 +1,128 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BDSM Game – Einladung – XXX The Game</title>
|
||||
<link rel="stylesheet" href="/css/variables.css">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<style>
|
||||
.invite-card {
|
||||
background: var(--color-card);
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-radius: 14px;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
max-width: 420px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.invite-icon { font-size: 2.5rem; margin-bottom: 1rem; }
|
||||
.invite-title { font-size: 1.2rem; font-weight: 700; margin-bottom: 0.5rem; }
|
||||
.invite-sub { font-size: 0.9rem; color: var(--color-muted); margin-bottom: 2rem; line-height: 1.6; }
|
||||
.invite-actions { display: flex; flex-direction: column; gap: 0.75rem; }
|
||||
.invite-actions button { width: 100%; padding: 0.85rem; }
|
||||
.decline-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-muted);
|
||||
font-size: 0.82rem;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
padding: 0.25rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="app">
|
||||
<div class="main">
|
||||
<div class="content">
|
||||
<div id="loading" style="text-align:center;color:var(--color-muted);padding:3rem 0;">Einladung wird geladen…</div>
|
||||
<div class="invite-card" id="card" style="display:none;">
|
||||
<div class="invite-icon">⛓️</div>
|
||||
<div class="invite-title" id="title"></div>
|
||||
<div class="invite-sub" id="sub"></div>
|
||||
<div class="message" id="message" style="display:none;margin-bottom:1rem;"></div>
|
||||
<div class="invite-actions" id="actions"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/js/sidebar.js"></script>
|
||||
<script>
|
||||
const params = new URLSearchParams(location.search);
|
||||
const einladungId = params.get('id');
|
||||
if (!einladungId) window.location.replace('/userhome.html');
|
||||
|
||||
let einladung = null;
|
||||
|
||||
async function laden() {
|
||||
try {
|
||||
const res = await fetch(`/bdsm/einladung/${einladungId}`);
|
||||
if (!res.ok) { zeigeFehler('Einladung nicht gefunden.'); return; }
|
||||
einladung = await res.json();
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
document.getElementById('card').style.display = '';
|
||||
|
||||
if (einladung.status === 'ACCEPTED_OWN' || einladung.status === 'ACCEPTED_HOST') {
|
||||
zeigeBestaetigt();
|
||||
return;
|
||||
}
|
||||
if (einladung.status === 'DECLINED' || einladung.status === 'CANCELLED') {
|
||||
zeigeFehler('Diese Einladung ist nicht mehr gültig.');
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('title').textContent = `${einladung.inviterName || 'Jemand'} lädt dich ein`;
|
||||
document.getElementById('sub').textContent = 'Du wurdest zu einem BDSM Game eingeladen. Wie möchtest du mitspielen?';
|
||||
const actions = document.getElementById('actions');
|
||||
actions.innerHTML = `
|
||||
<button onclick="antworten(true, 'OWN_DEVICE')">Am eigenen Gerät mitspielen</button>
|
||||
<button class="secondary" onclick="antworten(true, 'HOST_DEVICE')">Am Gerät von ${einladung.inviterName || 'der einladenden Person'}</button>
|
||||
<button class="decline-btn" onclick="antworten(false, null)">Einladung ablehnen</button>`;
|
||||
} catch (e) {
|
||||
zeigeFehler('Fehler beim Laden der Einladung.');
|
||||
}
|
||||
}
|
||||
|
||||
async function antworten(accepted, mode) {
|
||||
document.getElementById('actions').innerHTML = '<div style="color:var(--color-muted);font-size:0.9rem;">Wird gespeichert…</div>';
|
||||
try {
|
||||
const res = await fetch(`/bdsm/einladung/${einladungId}/antwort`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ accepted, mode }),
|
||||
});
|
||||
if (!res.ok) throw new Error();
|
||||
if (!accepted) {
|
||||
document.getElementById('title').textContent = 'Einladung abgelehnt';
|
||||
document.getElementById('sub').textContent = 'Du hast die Einladung abgelehnt.';
|
||||
document.getElementById('actions').innerHTML = '<button onclick="window.location.href=\'/userhome.html\'">Zur Startseite</button>';
|
||||
} else if (mode === 'OWN_DEVICE') {
|
||||
window.location.replace(`/bdsmwarten.html?id=${einladungId}`);
|
||||
} else {
|
||||
zeigeBestaetigt();
|
||||
}
|
||||
} catch (_) {
|
||||
document.getElementById('actions').innerHTML = '';
|
||||
zeigeFehler('Fehler beim Speichern der Antwort.');
|
||||
}
|
||||
}
|
||||
|
||||
function zeigeBestaetigt() {
|
||||
document.getElementById('title').textContent = 'Einladung angenommen';
|
||||
document.getElementById('sub').textContent = 'Du spielst am Gerät der einladenden Person mit. Das Spiel wird dort von ihr gestartet.';
|
||||
document.getElementById('actions').innerHTML = '<button onclick="window.location.href=\'/userhome.html\'">Zur Startseite</button>';
|
||||
}
|
||||
|
||||
function zeigeFehler(text) {
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
document.getElementById('card').style.display = '';
|
||||
document.getElementById('title').textContent = 'Hinweis';
|
||||
document.getElementById('sub').textContent = text;
|
||||
document.getElementById('actions').innerHTML = '<button onclick="window.location.href=\'/userhome.html\'">Zur Startseite</button>';
|
||||
}
|
||||
|
||||
laden();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,14 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BDSM Game – Neue Session – XXX The Game</title>
|
||||
<link rel="stylesheet" href="/css/variables.css">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<style>
|
||||
.session-setup { max-width: 540px; }
|
||||
.session-setup { }
|
||||
|
||||
.setup-section { margin-bottom: 2.5rem; }
|
||||
.setup-section h2 {
|
||||
@@ -180,7 +180,7 @@
|
||||
aufgabenProLevel: parseInt(document.getElementById('sldAufgaben').value),
|
||||
zeitfaktorZeitstrafen: parseInt(document.getElementById('sldZeit').value) / 10,
|
||||
}));
|
||||
window.location.href = '/sessionbdsmplayers.html';
|
||||
window.location.href = '/bdsmplayers.html';
|
||||
}
|
||||
|
||||
function showMessage(text, type) {
|
||||
@@ -224,7 +224,7 @@
|
||||
function sessionFortfahren(sid) {
|
||||
BDSM_STORAGE_KEYS.forEach(k => sessionStorage.removeItem(k));
|
||||
sessionStorage.setItem('bdsm-session-id', sid);
|
||||
window.location.href = '/sessionbdsmingame.html';
|
||||
window.location.href = '/bdsmingame.html';
|
||||
}
|
||||
|
||||
function sessionBeendenFragen(sid) {
|
||||
@@ -241,7 +241,7 @@
|
||||
async function sessionLoeschen(sid) {
|
||||
versteckeModal();
|
||||
try {
|
||||
await fetch('/session', {
|
||||
await fetch('/bdsm', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ sessionId: sid }),
|
||||
@@ -256,7 +256,7 @@
|
||||
if (!meRes.ok) return;
|
||||
const user = await meRes.json();
|
||||
|
||||
const sessionRes = await fetch(`/session?userId=${user.userId}`);
|
||||
const sessionRes = await fetch(`/bdsm?userId=${user.userId}`);
|
||||
if (sessionRes.status === 204) return;
|
||||
if (!sessionRes.ok) return;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BDSM Game – Im Spiel – XXX The Game</title>
|
||||
@@ -236,7 +236,12 @@
|
||||
const setup = JSON.parse(sessionStorage.getItem('bdsm-session-setup') || 'null');
|
||||
const toys = JSON.parse(sessionStorage.getItem('bdsm-session-toys') || '[]');
|
||||
const sessionId = sessionStorage.getItem('bdsm-session-id');
|
||||
if (!sessionId) window.location.replace('/sessionbdsm.html');
|
||||
if (!sessionId) window.location.replace('/bdsm.html');
|
||||
|
||||
// Multi-Device: bin ich Gast?
|
||||
const isGuest = sessionStorage.getItem('bdsm-is-guest') === 'true';
|
||||
const myMitspielerId = sessionStorage.getItem('bdsm-guest-mitspieler-id') || null;
|
||||
let guestPollInterval = null;
|
||||
|
||||
// ── Modal ──
|
||||
function zeigeModal(title, text, actions) {
|
||||
@@ -283,6 +288,7 @@
|
||||
|
||||
function clearTimer() {
|
||||
if (timerInterval) { clearInterval(timerInterval); timerInterval = null; }
|
||||
stopHostPoll();
|
||||
}
|
||||
|
||||
function zeigeTaskFehler(text) {
|
||||
@@ -310,7 +316,7 @@
|
||||
card.innerHTML = 'Aufgabe wird geladen…';
|
||||
|
||||
try {
|
||||
const res = await fetch(`/session/${sessionId}/aufgaben/next`);
|
||||
const res = await fetch(`/bdsm/${sessionId}/aufgaben/next`);
|
||||
if (res.status === 204) { zeigeFinaleDialog(); return; }
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
currentTask = await res.json();
|
||||
@@ -326,9 +332,189 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ── Aktive Aufgabe persistieren ──
|
||||
async function saveAktiveAufgabe(task, timerStartedAt) {
|
||||
try {
|
||||
await fetch(`/bdsm/${sessionId}/active-task`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ taskJson: JSON.stringify(task), timerStartedAt }),
|
||||
});
|
||||
} catch (_) { /* ignorieren */ }
|
||||
}
|
||||
|
||||
async function clearAktiveAufgabe() {
|
||||
try {
|
||||
await fetch(`/bdsm/${sessionId}/active-task`, { method: 'DELETE' });
|
||||
} catch (_) { /* ignorieren */ }
|
||||
}
|
||||
|
||||
async function checkAktiveAufgabe() {
|
||||
try {
|
||||
const res = await fetch(`/bdsm/${sessionId}/active-task`);
|
||||
if (res.status === 204 || !res.ok) { ladeAufgabe(); return; }
|
||||
const data = await res.json();
|
||||
currentTask = JSON.parse(data.taskJson);
|
||||
if (currentTask.level) {
|
||||
document.getElementById('levelImg').src = `/img/lvl${currentTask.level}.png`;
|
||||
document.getElementById('levelDisplay').style.display = '';
|
||||
}
|
||||
if (data.elapsedSeconds !== null && data.elapsedSeconds !== undefined) {
|
||||
const remaining = currentTask.timer - data.elapsedSeconds;
|
||||
if (remaining <= 0) {
|
||||
await clearAktiveAufgabe();
|
||||
aufgabeAbgeschlossen();
|
||||
} else {
|
||||
restoreTimer(Math.floor(remaining));
|
||||
}
|
||||
} else {
|
||||
zeigeAufgabe();
|
||||
}
|
||||
} catch (_) { ladeAufgabe(); }
|
||||
}
|
||||
|
||||
function restoreTimer(remaining) {
|
||||
const task = currentTask;
|
||||
const card = document.getElementById('taskCard');
|
||||
card.className = 'task-card';
|
||||
card.innerHTML = `
|
||||
${badgeHtml(task.nameAktiverMitspieler)}
|
||||
<div class="task-text" title="${escapeAttr(task.aufgabeText)}">${task.aufgabeText}</div>
|
||||
<div class="task-footer">
|
||||
<div class="task-timer-row" id="taskActions">
|
||||
<div class="timer-big" id="timerValue">${formatTime(remaining)}</div>
|
||||
<button class="btn-sm-cancel" onclick="timerAbbrechen()">✕ Abbrechen</button>
|
||||
</div>
|
||||
${SESSION_BEENDEN_BTN}
|
||||
</div>`;
|
||||
let rem = remaining;
|
||||
timerInterval = setInterval(() => {
|
||||
rem--;
|
||||
const el = document.getElementById('timerValue');
|
||||
if (rem <= 0) {
|
||||
clearTimer();
|
||||
if (el) { el.textContent = formatTime(0); el.classList.add('expired'); }
|
||||
aufgabeAbgeschlossen();
|
||||
} else {
|
||||
if (el) el.textContent = formatTime(rem);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// ── Gast-Polling: wartet auf aktive Aufgabe für mein Gerät ──
|
||||
function startGastPoll() {
|
||||
if (guestPollInterval) return;
|
||||
const card = document.getElementById('taskCard');
|
||||
card.className = 'task-card loading';
|
||||
card.innerHTML = 'Warte auf Aufgabe…';
|
||||
guestPollInterval = setInterval(pollGastAufgabe, 2500);
|
||||
}
|
||||
|
||||
async function pollGastAufgabe() {
|
||||
try {
|
||||
const res = await fetch(`/bdsm/${sessionId}/active-task`);
|
||||
if (res.status === 204) {
|
||||
// Keine aktive Aufgabe – warten
|
||||
const card = document.getElementById('taskCard');
|
||||
if (!card.classList.contains('loading')) {
|
||||
card.className = 'task-card loading';
|
||||
card.innerHTML = 'Warte auf Aufgabe…';
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
const task = JSON.parse(data.taskJson);
|
||||
|
||||
if (task.mitspielerId && task.mitspielerId === myMitspielerId) {
|
||||
// Meine Aufgabe!
|
||||
clearInterval(guestPollInterval); guestPollInterval = null;
|
||||
currentTask = task;
|
||||
if (task.level) {
|
||||
document.getElementById('levelImg').src = `/img/lvl${task.level}.png`;
|
||||
document.getElementById('levelDisplay').style.display = '';
|
||||
}
|
||||
if (data.elapsedSeconds !== null && data.elapsedSeconds !== undefined && task.timer != null) {
|
||||
const remaining = task.timer - data.elapsedSeconds;
|
||||
if (remaining <= 0) { await clearAktiveAufgabe(); gastAufgabeAbgeschlossen(); }
|
||||
else restoreTimer(Math.floor(remaining));
|
||||
} else {
|
||||
zeigeGastAufgabe(task);
|
||||
}
|
||||
} else {
|
||||
// Jemand anderes ist dran
|
||||
const card = document.getElementById('taskCard');
|
||||
const name = task.nameAktiverMitspieler || 'Jemand';
|
||||
card.className = 'task-card loading';
|
||||
card.innerHTML = `<div style="font-size:1rem;color:var(--color-muted);">${name} ist dran…</div>`;
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function zeigeGastAufgabe(task) {
|
||||
// Gast sieht seine Aufgabe mit eigenem "Erledigt"-Button
|
||||
const cb = task.callback;
|
||||
if (task.timer != null && !data?.elapsedSeconds) zeigeTimerAufgabe(task);
|
||||
else if (cb && cb.sperreId != null) zeigeSperreAufgabe(task);
|
||||
else if (cb && cb.faktor != null) zeigeVerlaengernAufgabe(task);
|
||||
else zeigeEinfacheAufgabe(task);
|
||||
}
|
||||
|
||||
async function gastAufgabeAbgeschlossen() {
|
||||
clearTimer();
|
||||
await clearAktiveAufgabe();
|
||||
const card = document.getElementById('taskCard');
|
||||
card.className = 'task-card loading';
|
||||
card.innerHTML = 'Aufgabe erledigt – warte auf nächste Aufgabe…';
|
||||
startGastPoll();
|
||||
}
|
||||
|
||||
// ── Host: wenn Aufgabe einem eigenem-Gerät-Spieler gehört ──
|
||||
let hostPollInterval = null;
|
||||
|
||||
function startHostPoll() {
|
||||
if (hostPollInterval) return;
|
||||
hostPollInterval = setInterval(pollHostAktiv, 2500);
|
||||
}
|
||||
|
||||
function stopHostPoll() {
|
||||
if (hostPollInterval) { clearInterval(hostPollInterval); hostPollInterval = null; }
|
||||
}
|
||||
|
||||
async function pollHostAktiv() {
|
||||
try {
|
||||
const res = await fetch(`/bdsm/${sessionId}/active-task`);
|
||||
if (res.status === 204) {
|
||||
// Gast hat Aufgabe erledigt → Host lädt nächste
|
||||
stopHostPoll();
|
||||
ladeAufgabe();
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function zeigeAufgabe() {
|
||||
const task = currentTask;
|
||||
const cb = task.callback;
|
||||
saveAktiveAufgabe(task, null);
|
||||
|
||||
// Wenn Aufgabe für eigenes-Gerät-Spieler: Host zeigt nur Hinweis
|
||||
if (!isGuest && task.eigenesGeraet && task.mitspielerId) {
|
||||
const name = task.nameAktiverMitspieler || 'Jemand';
|
||||
const card = document.getElementById('taskCard');
|
||||
card.className = 'task-card';
|
||||
card.innerHTML = `
|
||||
<div style="flex:1;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:0.5rem;">
|
||||
<div style="font-size:1.8rem;">📱</div>
|
||||
<div style="font-size:1rem;color:var(--color-muted);text-align:center;">${name} spielt gerade auf dem eigenen Gerät.</div>
|
||||
</div>
|
||||
<div class="task-footer">
|
||||
<div class="task-btns"></div>
|
||||
${SESSION_BEENDEN_BTN}
|
||||
</div>`;
|
||||
startHostPoll();
|
||||
return;
|
||||
}
|
||||
|
||||
if (cb && cb.sperreId != null) zeigeSperreAufgabe(task);
|
||||
else if (cb && cb.faktor != null) zeigeVerlaengernAufgabe(task);
|
||||
else if (task.timer != null) zeigeTimerAufgabe(task);
|
||||
@@ -380,6 +566,7 @@
|
||||
actions.innerHTML = `
|
||||
<div class="timer-big" id="timerValue">${formatTime(remaining)}</div>
|
||||
<button class="btn-sm-cancel" onclick="timerAbbrechen()">✕ Abbrechen</button>`;
|
||||
saveAktiveAufgabe(task, new Date().toISOString());
|
||||
|
||||
timerInterval = setInterval(() => {
|
||||
remaining--;
|
||||
@@ -405,8 +592,10 @@
|
||||
|
||||
async function aufgabeAbgeschlossen() {
|
||||
clearTimer();
|
||||
await clearAktiveAufgabe();
|
||||
if (isGuest) { gastAufgabeAbgeschlossen(); return; }
|
||||
try {
|
||||
const res = await fetch(`/session/sperre/abgelaufene?sessionId=${sessionId}`);
|
||||
const res = await fetch(`/bdsm/sperre/abgelaufene?sessionId=${sessionId}`);
|
||||
if (res.ok) {
|
||||
const text = await res.text();
|
||||
const texte = text.split(';').map(t => t.trim()).filter(t => t.length > 0);
|
||||
@@ -429,7 +618,7 @@
|
||||
const cb = currentTask?.callback;
|
||||
if (!cb) return;
|
||||
try {
|
||||
const res = await fetch('/session/sperre', {
|
||||
const res = await fetch('/bdsm/sperre', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ...cb, sessionId }),
|
||||
@@ -445,7 +634,7 @@
|
||||
const cb = currentTask?.callback;
|
||||
if (!cb) return;
|
||||
try {
|
||||
const res = await fetch('/session/sperre/verlaengern', {
|
||||
const res = await fetch('/bdsm/sperre/verlaengern', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ...cb, sessionId }),
|
||||
@@ -478,14 +667,14 @@
|
||||
|
||||
async function zurueckZuLevel5() {
|
||||
try {
|
||||
await fetch(`/session/${sessionId}/backToLevel5`, { method: 'POST' });
|
||||
await fetch(`/bdsm/${sessionId}/backToLevel5`, { method: 'POST' });
|
||||
} catch (_) {}
|
||||
ladeAufgabe();
|
||||
}
|
||||
|
||||
async function starteFinale() {
|
||||
try {
|
||||
const res = await fetch(`/session/sperre/aktive?sessionId=${sessionId}`);
|
||||
const res = await fetch(`/bdsm/sperre/aktive?sessionId=${sessionId}`);
|
||||
if (res.ok) {
|
||||
const sperren = await res.json();
|
||||
const texte = (sperren || []).map(s => s.releaseText).filter(t => t);
|
||||
@@ -506,7 +695,7 @@
|
||||
|
||||
async function ladeFinisher() {
|
||||
try {
|
||||
const res = await fetch(`/session/${sessionId}/finisher`);
|
||||
const res = await fetch(`/bdsm/${sessionId}/finisher`);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const liste = await res.json();
|
||||
naechsterFinisher(liste, 0);
|
||||
@@ -572,7 +761,7 @@
|
||||
async function sessionLoeschen() {
|
||||
versteckeModal();
|
||||
try {
|
||||
await fetch('/session', {
|
||||
await fetch('/bdsm', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ sessionId }),
|
||||
@@ -583,7 +772,11 @@
|
||||
}
|
||||
|
||||
// ── Start ──
|
||||
ladeAufgabe();
|
||||
if (isGuest) {
|
||||
startGastPoll();
|
||||
} else {
|
||||
checkAktiveAufgabe();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
743
xxxthegame/src/main/resources/static/bdsmplayers.html
Normal file
743
xxxthegame/src/main/resources/static/bdsmplayers.html
Normal file
@@ -0,0 +1,743 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BDSM Game – Mitspieler – XXX The Game</title>
|
||||
<link rel="stylesheet" href="/css/variables.css">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<style>
|
||||
.session-setup { }
|
||||
|
||||
.setup-section { margin-bottom: 2.5rem; }
|
||||
.setup-section h2 {
|
||||
color: var(--color-primary);
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1.25rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--color-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* ── Player cards ── */
|
||||
.player-card {
|
||||
background: var(--color-card);
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-radius: 10px;
|
||||
padding: 1.25rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.player-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.player-title {
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
font-size: 1rem;
|
||||
}
|
||||
.player-badge {
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
font-size: 0.7rem;
|
||||
padding: 0.1rem 0.5rem;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.player-badge-pending {
|
||||
background: var(--color-secondary);
|
||||
color: var(--color-muted);
|
||||
font-size: 0.7rem;
|
||||
padding: 0.1rem 0.5rem;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.player-badge-accepted {
|
||||
background: #1a5c2a;
|
||||
color: #6fcf97;
|
||||
font-size: 0.7rem;
|
||||
padding: 0.1rem 0.5rem;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.player-remove {
|
||||
margin-left: auto;
|
||||
background: transparent;
|
||||
border: 1px solid var(--color-secondary);
|
||||
color: var(--color-muted);
|
||||
padding: 0.25rem 0.6rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: normal;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s, color 0.15s;
|
||||
}
|
||||
.player-remove:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
background: transparent;
|
||||
}
|
||||
.btn-invite {
|
||||
background: transparent;
|
||||
border: 1px solid var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
padding: 0.45rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
.btn-invite:hover { background: var(--color-primary); color: #fff; }
|
||||
.btn-cancel-invite {
|
||||
background: transparent;
|
||||
border: 1px solid var(--color-secondary);
|
||||
color: var(--color-muted);
|
||||
padding: 0.2rem 0.6rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: normal;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn-cancel-invite:hover { border-color: var(--color-primary); color: var(--color-primary); background: transparent; }
|
||||
|
||||
.pending-info {
|
||||
text-align: center;
|
||||
color: var(--color-muted);
|
||||
font-size: 0.9rem;
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
.pending-name {
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.pending-mode {
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.card-field { margin-bottom: 1rem; }
|
||||
.card-field > label {
|
||||
font-size: 0.8rem;
|
||||
color: #aaa;
|
||||
margin: 0 0 0.5rem 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.check-group { display: flex; flex-wrap: wrap; gap: 0.5rem; }
|
||||
.check-group--two-col { display: grid; grid-template-columns: 1fr 1fr; }
|
||||
.check-item {
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.45rem;
|
||||
background: var(--color-secondary);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
padding: 0.4rem 0.7rem;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s;
|
||||
user-select: none;
|
||||
}
|
||||
.check-item.is-checked { border-color: var(--color-primary); }
|
||||
.check-item input { accent-color: var(--color-primary); width: auto; margin-top: 0.15rem; cursor: pointer; flex-shrink: 0; }
|
||||
.check-item-label { font-size: 0.88rem; color: var(--color-text); line-height: 1.3; }
|
||||
.check-item-desc { display: block; font-size: 0.72rem; color: var(--color-muted); margin-top: 0.1rem; }
|
||||
|
||||
.add-player-btn {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border: 1px dashed var(--color-secondary);
|
||||
color: var(--color-muted);
|
||||
padding: 0.75rem;
|
||||
border-radius: 10px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s, color 0.15s;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.add-player-btn:hover { border-color: var(--color-primary); color: var(--color-text); background: transparent; }
|
||||
|
||||
.field-error { font-size: 0.78rem; color: var(--color-primary); margin-top: 0.3rem; display: none; }
|
||||
|
||||
/* ── Freunde-Modal ── */
|
||||
.modal-overlay {
|
||||
position: fixed; inset: 0;
|
||||
background: rgba(0,0,0,0.75);
|
||||
z-index: 1000;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
.modal-card {
|
||||
background: var(--color-card);
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-radius: 14px;
|
||||
padding: 1.75rem;
|
||||
max-width: 420px; width: 100%;
|
||||
}
|
||||
.modal-title { font-size: 1rem; font-weight: 700; margin-bottom: 1rem; }
|
||||
.check-item.is-disabled { opacity: 0.5; pointer-events: none; cursor: default; }
|
||||
.friend-avatar { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; background: var(--color-secondary); flex-shrink: 0; }
|
||||
.friend-combobox { position: relative; }
|
||||
.friend-dropdown {
|
||||
display: none; position: absolute; top: 100%; left: 0; right: 0;
|
||||
background: var(--color-card); border: 1px solid var(--color-secondary);
|
||||
border-radius: 8px; max-height: 220px; overflow-y: auto; z-index: 10; margin-top: 0.25rem;
|
||||
}
|
||||
.friend-dropdown-item {
|
||||
display: flex; align-items: center; gap: 0.75rem;
|
||||
padding: 0.6rem 0.75rem; cursor: pointer; transition: background 0.1s;
|
||||
font-size: 0.9rem; font-weight: 600;
|
||||
}
|
||||
.friend-dropdown-item:hover { background: var(--color-secondary); }
|
||||
.selected-friend-box {
|
||||
display: none; margin-top: 0.75rem; padding: 0.6rem 0.75rem;
|
||||
background: var(--color-secondary); border-radius: 8px;
|
||||
font-size: 0.9rem; font-weight: 600;
|
||||
border: 1px solid var(--color-primary); color: var(--color-text);
|
||||
}
|
||||
.modal-cancel { margin-top: 0.6rem; width: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="app">
|
||||
|
||||
<div class="modal-overlay" id="friendModal" style="display:none;">
|
||||
<div class="modal-card">
|
||||
<div class="modal-title">Freund einladen</div>
|
||||
<div class="friend-combobox">
|
||||
<input type="text" id="friendSearch" placeholder="Name eingeben…" autocomplete="off" oninput="filterFreunde(this.value)">
|
||||
<div class="friend-dropdown" id="friendDropdown"></div>
|
||||
</div>
|
||||
<div class="selected-friend-box" id="selectedFriendBox"></div>
|
||||
<button id="btnEinladen" style="margin-top:1rem; width:100%;" disabled onclick="confirmedEinladen()">Einladen</button>
|
||||
<button class="secondary modal-cancel" onclick="schliesseFriendModal()">Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="content session-setup">
|
||||
|
||||
<h1>BDSM Game</h1>
|
||||
<p style="margin-bottom:2rem;">Schritt 2 von 4 – Mitspieler</p>
|
||||
|
||||
<div class="setup-section">
|
||||
<h2>Mitspieler</h2>
|
||||
<div id="playersContainer"></div>
|
||||
<button class="add-player-btn" onclick="addPlayer()">+ Spieler hinzufügen</button>
|
||||
</div>
|
||||
|
||||
<div class="message" id="message"></div>
|
||||
<div style="display:flex; gap:1rem;">
|
||||
<button style="flex:1;" class="secondary" onclick="window.location.href='/bdsm.html'">← Zurück</button>
|
||||
<button style="flex:2;" id="weiterBtn" onclick="weiter()">Weiter</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/sidebar.js"></script>
|
||||
<script>
|
||||
if (!sessionStorage.getItem('bdsm-session-settings')) {
|
||||
window.location.replace('/bdsm.html');
|
||||
}
|
||||
|
||||
// SetupId erzeugen (persistent über sessionStorage)
|
||||
if (!sessionStorage.getItem('bdsm-setup-id')) {
|
||||
sessionStorage.setItem('bdsm-setup-id', crypto.randomUUID());
|
||||
}
|
||||
const setupId = sessionStorage.getItem('bdsm-setup-id');
|
||||
|
||||
const GESCHLECHTER = [
|
||||
{ value: 'MAENNLICH', label: 'Männlich' },
|
||||
{ value: 'WEIBLICH', label: 'Weiblich' },
|
||||
{ value: 'DIVERS', label: 'Divers' },
|
||||
];
|
||||
const ROLLEN = [
|
||||
{ value: 'AUFGABE_AKTIV', label: 'Aufgabe – Aktiv' },
|
||||
{ value: 'AUFGABE_PASSIV', label: 'Aufgabe – Passiv' },
|
||||
{ value: 'BESTRAFUNG_AKTIV', label: 'Bestrafung – Aktiv' },
|
||||
{ value: 'BESTRAFUNG_PASSIV', label: 'Bestrafung – Passiv' },
|
||||
];
|
||||
const WERKZEUGE_DEFAULTS = {
|
||||
MAENNLICH: ['MUND', 'PENIS', 'ANUS', 'UMSCHNALLDILDO'],
|
||||
WEIBLICH: ['MUND', 'VAGINA', 'ANUS', 'UMSCHNALLDILDO'],
|
||||
DIVERS: ['MUND', 'ANUS', 'UMSCHNALLDILDO'],
|
||||
};
|
||||
const WERKZEUGE = [
|
||||
{ value: 'MUND', label: 'Mund', desc: 'Gewillt den Mund einzusetzen' },
|
||||
{ value: 'VAGINA', label: 'Vagina', desc: 'Verfügt über eine Vagina und setzt sie ein' },
|
||||
{ value: 'PENIS', label: 'Penis', desc: 'Verfügt über einen Penis und setzt ihn ein' },
|
||||
{ value: 'ANUS', label: 'Anus', desc: 'Gewillt den Anus einzusetzen' },
|
||||
{ value: 'UMSCHNALLDILDO', label: 'Umschnall-Dildo', desc: 'Verfügt über einen Umschnall-Dildo' },
|
||||
];
|
||||
const ROLE_LABELS = {
|
||||
AUFGABE_AKTIV: 'Aufgabe – Aktiv', AUFGABE_PASSIV: 'Aufgabe – Passiv',
|
||||
BESTRAFUNG_AKTIV: 'Bestrafung – Aktiv', BESTRAFUNG_PASSIV: 'Bestrafung – Passiv',
|
||||
};
|
||||
|
||||
let playerSeq = 0;
|
||||
let playerIds = [];
|
||||
// { [playerId]: { einladungId, status, inviteeId, inviteeName, mode } | null }
|
||||
let playerInvitations = {};
|
||||
let pollIntervalId = null;
|
||||
let myUserId = null;
|
||||
let freundeListe = [];
|
||||
|
||||
function buildCheckItems(name, items, type, disabled = false) {
|
||||
return items.map(({ value, label, desc }) => `
|
||||
<label class="check-item${disabled ? ' is-disabled' : ''}">
|
||||
<input type="${type}" name="${name}" value="${value}"${disabled ? ' disabled' : ''}>
|
||||
<span>
|
||||
<span class="check-item-label">${label}</span>
|
||||
${desc ? `<span class="check-item-desc">${desc}</span>` : ''}
|
||||
</span>
|
||||
</label>`).join('');
|
||||
}
|
||||
|
||||
function createCardHtml(id, prefillName, isSelf) {
|
||||
const badge = isSelf ? '<span class="player-badge">Du</span>' : '';
|
||||
const num = playerIds.indexOf(id) + 1;
|
||||
const nameField = isSelf
|
||||
? `<input type="text" id="p${id}-name" value="${prefillName}" readonly style="background:transparent;cursor:default;color:var(--color-muted);">`
|
||||
: `<input type="text" id="p${id}-name" value="${prefillName}" placeholder="Name" autocomplete="off">`;
|
||||
const inviteBtn = isSelf ? '' : `<button class="btn-invite" onclick="oeffneFreundeModal(${id})">👥 Einladen</button>`;
|
||||
return `
|
||||
<div class="player-card" id="player-${id}">
|
||||
<div class="player-card-header">
|
||||
<span class="player-title">Spieler ${num}</span>
|
||||
${badge}
|
||||
${inviteBtn}
|
||||
<button class="player-remove" onclick="removePlayer(${id})">✕ Entfernen</button>
|
||||
</div>
|
||||
<div id="p${id}-body">
|
||||
${buildPlayerBody(id, nameField, isSelf)}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function buildPlayerBody(id, nameField, genderDisabled = false) {
|
||||
return `
|
||||
<div class="card-field">
|
||||
<label>Name</label>
|
||||
${nameField}
|
||||
<div class="field-error" id="p${id}-name-err">Bitte Namen eingeben.</div>
|
||||
</div>
|
||||
<div class="card-field">
|
||||
<label>Geschlecht${genderDisabled ? ' <span style="font-size:0.75rem;color:var(--color-muted);">(unveränderlich)</span>' : ''}</label>
|
||||
<div class="check-group">${buildCheckItems('p' + id + '-geschlecht', GESCHLECHTER, 'radio', genderDisabled)}</div>
|
||||
<div class="field-error" id="p${id}-geschlecht-err">Bitte Geschlecht auswählen.</div>
|
||||
</div>
|
||||
<div class="card-field">
|
||||
<label>Spielt mit</label>
|
||||
<div class="check-group">${buildCheckItems('p' + id + '-spieltmit', GESCHLECHTER, 'checkbox')}</div>
|
||||
<div class="field-error" id="p${id}-spieltmit-err">Bitte mindestens eine Option wählen.</div>
|
||||
<div class="field-error" id="p${id}-partner-err">Kein Mitspieler mit passendem Geschlecht vorhanden.</div>
|
||||
</div>
|
||||
<div class="card-field">
|
||||
<label>Rollen</label>
|
||||
<div class="check-group">${buildCheckItems('p' + id + '-rollen', ROLLEN, 'checkbox')}</div>
|
||||
<div class="field-error" id="p${id}-rollen-err">Bitte mindestens eine Rolle wählen.</div>
|
||||
</div>
|
||||
<div class="card-field">
|
||||
<label>Verfügbar</label>
|
||||
<div class="check-group check-group--two-col">${buildCheckItems('p' + id + '-werkzeuge', WERKZEUGE, 'checkbox')}</div>
|
||||
<div class="field-error" id="p${id}-werkzeuge-err">Bitte mindestens ein Werkzeug wählen.</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function addPlayer(prefillName = '', isSelf = false) {
|
||||
playerSeq++;
|
||||
const id = playerSeq;
|
||||
playerIds.push(id);
|
||||
playerInvitations[id] = null;
|
||||
document.getElementById('playersContainer')
|
||||
.insertAdjacentHTML('beforeend', createCardHtml(id, prefillName, isSelf));
|
||||
refreshRemoveButtons();
|
||||
return id;
|
||||
}
|
||||
|
||||
function removePlayer(id) {
|
||||
const inv = playerInvitations[id];
|
||||
if (inv && (inv.status === 'PENDING' || inv.status === 'ACCEPTED_OWN' || inv.status === 'ACCEPTED_HOST')) {
|
||||
cancelEinladung(id);
|
||||
}
|
||||
document.getElementById('player-' + id)?.remove();
|
||||
playerIds = playerIds.filter(x => x !== id);
|
||||
delete playerInvitations[id];
|
||||
refreshPlayerTitles();
|
||||
refreshRemoveButtons();
|
||||
}
|
||||
|
||||
function refreshPlayerTitles() {
|
||||
playerIds.forEach((id, idx) => {
|
||||
const el = document.querySelector(`#player-${id} .player-title`);
|
||||
if (el) el.textContent = 'Spieler ' + (idx + 1);
|
||||
});
|
||||
}
|
||||
|
||||
function refreshRemoveButtons() {
|
||||
const canRemove = playerIds.length > 2;
|
||||
playerIds.forEach(id => {
|
||||
const btn = document.querySelector(`#player-${id} .player-remove`);
|
||||
if (btn) btn.style.display = canRemove ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('change', e => {
|
||||
const input = e.target;
|
||||
if (input.type !== 'checkbox' && input.type !== 'radio') return;
|
||||
if (input.type === 'radio') {
|
||||
document.querySelectorAll(`input[name="${input.name}"]`).forEach(r => {
|
||||
r.closest('.check-item')?.classList.toggle('is-checked', r.checked);
|
||||
});
|
||||
if (input.checked && input.name.endsWith('-geschlecht')) {
|
||||
const prefix = input.name.slice(0, -'-geschlecht'.length);
|
||||
const defaults = WERKZEUGE_DEFAULTS[input.value] || [];
|
||||
document.querySelectorAll(`input[name="${prefix}-werkzeuge"]`).forEach(cb => {
|
||||
cb.checked = defaults.includes(cb.value);
|
||||
cb.closest('.check-item')?.classList.toggle('is-checked', cb.checked);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
input.closest('.check-item')?.classList.toggle('is-checked', input.checked);
|
||||
}
|
||||
});
|
||||
|
||||
function getChecked(name) {
|
||||
return [...document.querySelectorAll(`input[name="${name}"]:checked`)].map(el => el.value);
|
||||
}
|
||||
|
||||
function setFieldError(id, show) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.style.display = show ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// ── Freunde-Modal ──
|
||||
let currentInvitePlayerId = null;
|
||||
let selectedFriend = null; // { userId, name }
|
||||
|
||||
async function oeffneFreundeModal(playerId) {
|
||||
currentInvitePlayerId = playerId;
|
||||
selectedFriend = null;
|
||||
document.getElementById('friendSearch').value = '';
|
||||
document.getElementById('friendDropdown').style.display = 'none';
|
||||
document.getElementById('friendDropdown').innerHTML = '';
|
||||
document.getElementById('selectedFriendBox').style.display = 'none';
|
||||
document.getElementById('selectedFriendBox').textContent = '';
|
||||
document.getElementById('btnEinladen').disabled = true;
|
||||
document.getElementById('friendModal').style.display = 'flex';
|
||||
if (freundeListe.length === 0) {
|
||||
try {
|
||||
const res = await fetch('/social/friends');
|
||||
freundeListe = res.ok ? await res.json() : [];
|
||||
} catch (_) { freundeListe = []; }
|
||||
}
|
||||
}
|
||||
|
||||
function filterFreunde(query) {
|
||||
selectedFriend = null;
|
||||
document.getElementById('selectedFriendBox').style.display = 'none';
|
||||
document.getElementById('btnEinladen').disabled = true;
|
||||
const dropdown = document.getElementById('friendDropdown');
|
||||
dropdown.innerHTML = '';
|
||||
const q = query.trim().toLowerCase();
|
||||
if (!q) { dropdown.style.display = 'none'; return; }
|
||||
const matches = freundeListe.filter(f => (f.user.name || '').toLowerCase().includes(q));
|
||||
if (!matches.length) {
|
||||
dropdown.innerHTML = '<div style="padding:0.6rem 0.75rem;color:var(--color-muted);font-size:0.9rem;">Keine Treffer.</div>';
|
||||
dropdown.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
matches.forEach(f => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'friend-dropdown-item';
|
||||
item.addEventListener('click', () => selectFriend(f.user.userId, f.user.name || 'Unbekannt'));
|
||||
if (f.user.profilePicture) {
|
||||
const img = document.createElement('img');
|
||||
img.className = 'friend-avatar';
|
||||
img.src = 'data:image/png;base64,' + f.user.profilePicture;
|
||||
img.alt = '';
|
||||
item.appendChild(img);
|
||||
} else {
|
||||
const av = document.createElement('div');
|
||||
av.className = 'friend-avatar';
|
||||
item.appendChild(av);
|
||||
}
|
||||
const span = document.createElement('span');
|
||||
span.textContent = f.user.name || 'Unbekannt';
|
||||
item.appendChild(span);
|
||||
dropdown.appendChild(item);
|
||||
});
|
||||
dropdown.style.display = 'block';
|
||||
}
|
||||
|
||||
function selectFriend(userId, name) {
|
||||
selectedFriend = { userId, name };
|
||||
document.getElementById('friendSearch').value = name;
|
||||
document.getElementById('friendDropdown').style.display = 'none';
|
||||
const box = document.getElementById('selectedFriendBox');
|
||||
box.textContent = '✓ ' + name;
|
||||
box.style.display = 'block';
|
||||
document.getElementById('btnEinladen').disabled = false;
|
||||
}
|
||||
|
||||
async function confirmedEinladen() {
|
||||
if (!selectedFriend) return;
|
||||
await einladen(selectedFriend.userId, selectedFriend.name);
|
||||
}
|
||||
|
||||
function schliesseFriendModal() {
|
||||
document.getElementById('friendModal').style.display = 'none';
|
||||
currentInvitePlayerId = null;
|
||||
selectedFriend = null;
|
||||
}
|
||||
|
||||
async function einladen(inviteeId, inviteeName) {
|
||||
const id = currentInvitePlayerId;
|
||||
schliesseFriendModal();
|
||||
if (!id) return;
|
||||
try {
|
||||
const res = await fetch('/bdsm/einladung', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ setupId, slotIndex: id, inviteeId }),
|
||||
});
|
||||
if (!res.ok) throw new Error();
|
||||
const data = await res.json();
|
||||
playerInvitations[id] = { einladungId: data.einladungId, status: 'PENDING', inviteeId, inviteeName };
|
||||
renderPending(id);
|
||||
startPoll();
|
||||
} catch (_) {
|
||||
showMessage('Einladung konnte nicht gesendet werden.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function renderPending(id) {
|
||||
const inv = playerInvitations[id];
|
||||
if (!inv) return;
|
||||
const body = document.getElementById(`p${id}-body`);
|
||||
if (!body) return;
|
||||
const headerInvBtn = document.querySelector(`#player-${id} .btn-invite`);
|
||||
if (headerInvBtn) headerInvBtn.style.display = 'none';
|
||||
|
||||
if (inv.status === 'PENDING') {
|
||||
// Badge
|
||||
const header = document.querySelector(`#player-${id} .player-card-header`);
|
||||
header.querySelectorAll('.player-badge-pending,.player-badge-accepted').forEach(el => el.remove());
|
||||
header.insertAdjacentHTML('afterbegin', `<span class="player-badge-pending">Ausstehend</span>`);
|
||||
body.innerHTML = `
|
||||
<div class="pending-info">
|
||||
<div class="pending-name">${inv.inviteeName}</div>
|
||||
<div>Einladung wurde gesendet – warte auf Antwort…</div>
|
||||
<button class="btn-cancel-invite" style="margin-top:1rem;" onclick="cancelEinladung(${id})">Einladung abbrechen</button>
|
||||
</div>`;
|
||||
} else if (inv.status === 'ACCEPTED_OWN' || inv.status === 'ACCEPTED_HOST') {
|
||||
const header = document.querySelector(`#player-${id} .player-card-header`);
|
||||
header.querySelectorAll('.player-badge-pending,.player-badge-accepted').forEach(el => el.remove());
|
||||
const modeLabel = inv.status === 'ACCEPTED_OWN' ? 'Eigenes Gerät' : 'Host-Gerät';
|
||||
header.insertAdjacentHTML('afterbegin', `<span class="player-badge-accepted">✓ ${modeLabel}</span>`);
|
||||
// Name readonly, Geschlecht gesperrt (kommt vom Profil der eingeladenen Person)
|
||||
const nameField = `<input type="text" id="p${id}-name" value="${inv.inviteeName}" readonly style="background:transparent;cursor:default;color:var(--color-muted);">`;
|
||||
body.innerHTML = buildPlayerBody(id, nameField, true);
|
||||
// Defaults laden falls verfügbar
|
||||
if (inv.defaults) {
|
||||
restorePlayer(id, {
|
||||
geschlecht: inv.defaults.geschlecht,
|
||||
spieltMit: inv.defaults.spieltMit || [],
|
||||
rollen: inv.defaults.rollen || [],
|
||||
werkzeuge: inv.defaults.werkzeuge || [],
|
||||
});
|
||||
}
|
||||
} else if (inv.status === 'DECLINED' || inv.status === 'CANCELLED') {
|
||||
// Slot wieder freigeben
|
||||
const header = document.querySelector(`#player-${id} .player-card-header`);
|
||||
header.querySelectorAll('.player-badge-pending,.player-badge-accepted').forEach(el => el.remove());
|
||||
if (headerInvBtn) headerInvBtn.style.display = '';
|
||||
playerInvitations[id] = null;
|
||||
body.innerHTML = buildPlayerBody(id, `<input type="text" id="p${id}-name" placeholder="Name" autocomplete="off">`);
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelEinladung(id) {
|
||||
const inv = playerInvitations[id];
|
||||
if (!inv) return;
|
||||
await fetch(`/bdsm/einladung/${inv.einladungId}`, { method: 'DELETE' }).catch(() => {});
|
||||
playerInvitations[id] = null;
|
||||
// UI zurücksetzen
|
||||
const header = document.querySelector(`#player-${id} .player-card-header`);
|
||||
if (header) {
|
||||
header.querySelectorAll('.player-badge-pending,.player-badge-accepted').forEach(el => el.remove());
|
||||
const invBtn = header.querySelector('.btn-invite');
|
||||
if (invBtn) invBtn.style.display = '';
|
||||
}
|
||||
const body = document.getElementById(`p${id}-body`);
|
||||
if (body) body.innerHTML = buildPlayerBody(id, `<input type="text" id="p${id}-name" placeholder="Name" autocomplete="off">`);
|
||||
}
|
||||
|
||||
// ── Polling ──
|
||||
function startPoll() {
|
||||
if (pollIntervalId) return;
|
||||
pollIntervalId = setInterval(pollEinladungen, 3000);
|
||||
}
|
||||
|
||||
function stopPoll() {
|
||||
if (pollIntervalId) { clearInterval(pollIntervalId); pollIntervalId = null; }
|
||||
}
|
||||
|
||||
async function pollEinladungen() {
|
||||
const hasPending = Object.values(playerInvitations).some(inv => inv && inv.status === 'PENDING');
|
||||
if (!hasPending) { stopPoll(); return; }
|
||||
try {
|
||||
const res = await fetch(`/bdsm/einladung?setupId=${setupId}`);
|
||||
if (!res.ok) return;
|
||||
const liste = await res.json();
|
||||
for (const e of liste) {
|
||||
const id = playerIds.find(pid => {
|
||||
const inv = playerInvitations[pid];
|
||||
return inv && inv.einladungId === e.einladungId;
|
||||
});
|
||||
if (!id) continue;
|
||||
const inv = playerInvitations[id];
|
||||
if (!inv || inv.status === e.status) continue;
|
||||
inv.status = e.status;
|
||||
if (e.status === 'ACCEPTED_OWN' || e.status === 'ACCEPTED_HOST') {
|
||||
// Defaults der eingeladenen Person laden
|
||||
try {
|
||||
const dRes = await fetch(`/user/${inv.inviteeId}/bdsm-defaults`);
|
||||
if (dRes.ok) inv.defaults = await dRes.json();
|
||||
} catch (_) {}
|
||||
}
|
||||
renderPending(id);
|
||||
}
|
||||
} catch (_) {}
|
||||
updateWeiterBtn();
|
||||
}
|
||||
|
||||
function updateWeiterBtn() {
|
||||
const hasPending = Object.values(playerInvitations).some(inv => inv && inv.status === 'PENDING');
|
||||
document.getElementById('weiterBtn').disabled = hasPending;
|
||||
}
|
||||
|
||||
// ── Validation & Weiter ──
|
||||
function weiter() {
|
||||
hideMessage();
|
||||
|
||||
const hasPending = Object.values(playerInvitations).some(inv => inv && inv.status === 'PENDING');
|
||||
if (hasPending) {
|
||||
showMessage('Bitte warte, bis alle Einladungen beantwortet wurden.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
let valid = true;
|
||||
playerIds.forEach(id => setFieldError(`p${id}-partner-err`, false));
|
||||
|
||||
const mitspieler = playerIds.map(id => {
|
||||
const inv = playerInvitations[id];
|
||||
const name = document.getElementById(`p${id}-name`)?.value.trim() || '';
|
||||
const geschlecht = getChecked(`p${id}-geschlecht`);
|
||||
const spieltMit = getChecked(`p${id}-spieltmit`);
|
||||
const rollen = getChecked(`p${id}-rollen`);
|
||||
const werkzeuge = getChecked(`p${id}-werkzeuge`);
|
||||
|
||||
setFieldError(`p${id}-name-err`, !name);
|
||||
setFieldError(`p${id}-geschlecht-err`, geschlecht.length === 0);
|
||||
setFieldError(`p${id}-spieltmit-err`, spieltMit.length === 0);
|
||||
setFieldError(`p${id}-rollen-err`, rollen.length === 0);
|
||||
setFieldError(`p${id}-werkzeuge-err`, werkzeuge.length === 0);
|
||||
|
||||
if (!name || geschlecht.length === 0 || spieltMit.length === 0 || rollen.length === 0 || werkzeuge.length === 0) {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
geschlecht: geschlecht[0] || null,
|
||||
spieltMit, rollen, werkzeuge,
|
||||
userId: inv ? inv.inviteeId : null,
|
||||
eigenesGeraet: inv ? inv.status === 'ACCEPTED_OWN' : false,
|
||||
};
|
||||
});
|
||||
|
||||
if (!valid) { showMessage('Bitte alle Felder für jeden Spieler ausfüllen.', 'error'); return; }
|
||||
|
||||
const allRoles = new Set(mitspieler.flatMap(p => p.rollen));
|
||||
const missingRoles = Object.keys(ROLE_LABELS).filter(r => !allRoles.has(r));
|
||||
if (missingRoles.length > 0) {
|
||||
showMessage('Folgende Rollen müssen mindestens einmal vergeben sein: ' +
|
||||
missingRoles.map(r => ROLE_LABELS[r]).join(', '), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
let partnerFehler = false;
|
||||
mitspieler.forEach((player, i) => {
|
||||
const andereGeschlechter = mitspieler.filter((_, j) => j !== i).map(p => p.geschlecht);
|
||||
const hatPartner = player.spieltMit.some(g => andereGeschlechter.includes(g));
|
||||
if (!hatPartner) { setFieldError(`p${playerIds[i]}-partner-err`, true); partnerFehler = true; }
|
||||
});
|
||||
if (partnerFehler) { showMessage('Mindestens ein Spieler hat keinen kompatiblen Mitspieler.', 'error'); return; }
|
||||
|
||||
const settings = JSON.parse(sessionStorage.getItem('bdsm-session-settings'));
|
||||
sessionStorage.setItem('bdsm-session-setup', JSON.stringify({ settings, mitspieler }));
|
||||
window.location.href = '/bdsmtasks.html';
|
||||
}
|
||||
|
||||
function showMessage(text, type) {
|
||||
const el = document.getElementById('message');
|
||||
el.textContent = text; el.className = `message ${type}`; el.style.display = 'block';
|
||||
}
|
||||
function hideMessage() { document.getElementById('message').style.display = 'none'; }
|
||||
|
||||
function restorePlayer(id, data) {
|
||||
if (data.geschlecht) {
|
||||
const radio = document.querySelector(`input[name="p${id}-geschlecht"][value="${data.geschlecht}"]`);
|
||||
if (radio) { radio.checked = true; radio.closest('.check-item')?.classList.add('is-checked'); }
|
||||
}
|
||||
(data.spieltMit || []).forEach(val => {
|
||||
const cb = document.querySelector(`input[name="p${id}-spieltmit"][value="${val}"]`);
|
||||
if (cb) { cb.checked = true; cb.closest('.check-item')?.classList.add('is-checked'); }
|
||||
});
|
||||
(data.rollen || []).forEach(val => {
|
||||
const cb = document.querySelector(`input[name="p${id}-rollen"][value="${val}"]`);
|
||||
if (cb) { cb.checked = true; cb.closest('.check-item')?.classList.add('is-checked'); }
|
||||
});
|
||||
(data.werkzeuge || []).forEach(val => {
|
||||
const cb = document.querySelector(`input[name="p${id}-werkzeuge"][value="${val}"]`);
|
||||
if (cb) { cb.checked = true; cb.closest('.check-item')?.classList.add('is-checked'); }
|
||||
});
|
||||
}
|
||||
|
||||
// ── Init ──
|
||||
const savedSetup = sessionStorage.getItem('bdsm-session-setup');
|
||||
if (savedSetup) {
|
||||
const { mitspieler } = JSON.parse(savedSetup);
|
||||
mitspieler.forEach((p, i) => {
|
||||
const id = addPlayer(p.name, i === 0);
|
||||
restorePlayer(id, p);
|
||||
});
|
||||
} else {
|
||||
Promise.all([
|
||||
fetch('/login/me').then(r => r.ok ? r.json() : null).catch(() => null),
|
||||
fetch('/user/me/bdsm-defaults').then(r => r.ok ? r.json() : {}).catch(() => ({}))
|
||||
]).then(([user, defaults]) => {
|
||||
myUserId = user?.userId || null;
|
||||
addPlayer(user ? user.name : '', true);
|
||||
addPlayer();
|
||||
const selfId = playerIds[0];
|
||||
restorePlayer(selfId, {
|
||||
geschlecht: user?.geschlecht || null,
|
||||
spieltMit: defaults.spieltMit || [],
|
||||
rollen: defaults.rollen || [],
|
||||
werkzeuge: defaults.werkzeuge || [],
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,14 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BDSM Game – Aufgaben-Gruppen – XXX The Game</title>
|
||||
<link rel="stylesheet" href="/css/variables.css">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<style>
|
||||
.session-setup { max-width: 700px; }
|
||||
.session-setup { }
|
||||
|
||||
.setup-section { margin-bottom: 2.5rem; }
|
||||
.setup-section h2 {
|
||||
@@ -119,7 +119,7 @@
|
||||
<div style="position:relative; margin-top:2rem;">
|
||||
<div class="message" id="message" style="position:absolute; bottom:calc(100% + 0.5rem); left:0; right:0; margin:0;"></div>
|
||||
<div style="display:flex; gap:1rem;">
|
||||
<button style="flex:1;" class="secondary" onclick="window.location.href='/sessionbdsmplayers.html'">← Zurück</button>
|
||||
<button style="flex:1;" class="secondary" onclick="window.location.href='/bdsmplayers.html'">← Zurück</button>
|
||||
<button style="flex:2;" onclick="weiter()">Weiter</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -130,7 +130,7 @@
|
||||
<script src="/js/sidebar.js"></script>
|
||||
<script>
|
||||
if (!sessionStorage.getItem('bdsm-session-setup')) {
|
||||
window.location.replace('/sessionbdsm.html');
|
||||
window.location.replace('/bdsm.html');
|
||||
}
|
||||
|
||||
const savedGruppen = new Set(JSON.parse(sessionStorage.getItem('bdsm-session-gruppen') || '[]'));
|
||||
@@ -308,18 +308,19 @@
|
||||
}
|
||||
|
||||
sessionStorage.setItem('bdsm-session-gruppen', JSON.stringify(selected));
|
||||
window.location.href = '/sessionbdsmtoys.html';
|
||||
window.location.href = '/bdsmtoys.html';
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
fetch('/gruppe/list/user?page=0&size=500').then(r => r.ok ? r.json() : { content: [] }),
|
||||
fetch('/abo/list?page=0&size=500').then(r => r.ok ? r.json() : { content: [] }),
|
||||
fetch('/gruppe/list/system?page=0&size=500').then(r => r.ok ? r.json() : { content: [] }),
|
||||
fetch('/gruppe/list/user?page=0&size=500').then(r => r.ok ? r.json() : (console.warn('[bdsmtasks] user HTTP', r.status), { content: [] })),
|
||||
fetch('/abo/list?page=0&size=500').then(r => r.ok ? r.json() : (console.warn('[bdsmtasks] abo HTTP', r.status), { content: [] })),
|
||||
fetch('/gruppe/list/system?page=0&size=500').then(r => r.ok ? r.json() : (console.warn('[bdsmtasks] system HTTP', r.status), { content: [] })),
|
||||
]).then(([own, abo, system]) => {
|
||||
console.log('[bdsmtasks] own:', own, 'abo:', abo, 'system:', system);
|
||||
renderList('listOwn', own.content || []);
|
||||
renderList('listSubscribed', abo.content || []);
|
||||
renderList('listSystem', system.content || []);
|
||||
});
|
||||
}).catch(err => console.error('[bdsmtasks] Fehler beim Laden:', err));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,14 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BDSM Game – Toys – XXX The Game</title>
|
||||
<link rel="stylesheet" href="/css/variables.css">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<style>
|
||||
.session-setup { max-width: 700px; }
|
||||
.session-setup { }
|
||||
|
||||
.setup-section { margin-bottom: 2.5rem; }
|
||||
.setup-section h2 {
|
||||
@@ -74,7 +74,7 @@
|
||||
<div style="position:relative; margin-top:2rem;">
|
||||
<div class="message" id="message" style="position:absolute; bottom:calc(100% + 0.5rem); left:0; right:0; margin:0;"></div>
|
||||
<div style="display:flex; gap:1rem;">
|
||||
<button style="flex:1;" class="secondary" onclick="window.location.href='/sessionbdsmtasks.html'">← Zurück</button>
|
||||
<button style="flex:1;" class="secondary" onclick="window.location.href='/bdsmtasks.html'">← Zurück</button>
|
||||
<button style="flex:2;" onclick="spielStarten()">Spiel starten</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +85,7 @@
|
||||
<script src="/js/sidebar.js"></script>
|
||||
<script>
|
||||
const savedGruppen = JSON.parse(sessionStorage.getItem('bdsm-session-gruppen') || 'null');
|
||||
if (!savedGruppen) window.location.replace('/sessionbdsm.html');
|
||||
if (!savedGruppen) window.location.replace('/bdsm.html');
|
||||
|
||||
// Previously saved toy selection (when navigating back from game page)
|
||||
const savedToysRaw = sessionStorage.getItem('bdsm-session-toys');
|
||||
@@ -241,7 +241,7 @@
|
||||
|
||||
try {
|
||||
// 1. Session anlegen
|
||||
const sessionRes = await fetch('/session', {
|
||||
const sessionRes = await fetch('/bdsm', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -249,6 +249,7 @@
|
||||
wahrscheinlichkeitSperre: settings.wahrscheinlichkeitSperre,
|
||||
aufgabenProLevel: settings.aufgabenProLevel,
|
||||
zeitfaktorZeitstrafen: settings.zeitfaktorZeitstrafen,
|
||||
setupId: sessionStorage.getItem('bdsm-setup-id'),
|
||||
}),
|
||||
});
|
||||
if (!sessionRes.ok) throw new Error('Session konnte nicht angelegt werden.');
|
||||
@@ -258,7 +259,7 @@
|
||||
// 2. Mitspieler hinzufügen
|
||||
const setup = JSON.parse(sessionStorage.getItem('bdsm-session-setup'));
|
||||
for (const p of setup.mitspieler) {
|
||||
const res = await fetch(`/session/${sessionId}/mitspieler`, {
|
||||
const res = await fetch(`/bdsm/${sessionId}/mitspieler`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -267,13 +268,15 @@
|
||||
spieltMit: p.spieltMit,
|
||||
rollen: p.rollen,
|
||||
verfuegbareWerkzeuge: p.werkzeuge,
|
||||
userId: p.userId || null,
|
||||
eigenesGeraet: p.eigenesGeraet || false,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) throw new Error(`Mitspieler "${p.name}" konnte nicht hinzugefügt werden.`);
|
||||
}
|
||||
|
||||
// 3. Aufgaben setzen
|
||||
const aufgabenRes = await fetch(`/session/${sessionId}/aufgaben`, {
|
||||
const aufgabenRes = await fetch(`/bdsm/${sessionId}/aufgaben`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(gameContent),
|
||||
@@ -281,7 +284,7 @@
|
||||
if (!aufgabenRes.ok) throw new Error('Aufgaben konnten nicht gespeichert werden.');
|
||||
|
||||
sessionStorage.setItem('bdsm-session-id', sessionId);
|
||||
window.location.href = '/sessionbdsmingame.html';
|
||||
window.location.href = '/bdsmingame.html';
|
||||
} catch (e) {
|
||||
showMessage(e.message, 'error');
|
||||
btn.disabled = false;
|
||||
92
xxxthegame/src/main/resources/static/bdsmwarten.html
Normal file
92
xxxthegame/src/main/resources/static/bdsmwarten.html
Normal file
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BDSM Game – Warten – XXX The Game</title>
|
||||
<link rel="stylesheet" href="/css/variables.css">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<style>
|
||||
.wait-card {
|
||||
text-align: center;
|
||||
padding: 3rem 1rem;
|
||||
}
|
||||
.wait-icon { font-size: 3rem; margin-bottom: 1.5rem; animation: pulse 2s ease-in-out infinite; }
|
||||
@keyframes pulse { 0%,100% { opacity:1; } 50% { opacity:0.4; } }
|
||||
.wait-title { font-size: 1.3rem; font-weight: 700; margin-bottom: 0.75rem; }
|
||||
.wait-sub { font-size: 0.9rem; color: var(--color-muted); line-height: 1.6; margin-bottom: 2rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="app">
|
||||
<div class="main">
|
||||
<div class="content wait-card">
|
||||
<div class="wait-icon">⏳</div>
|
||||
<div class="wait-title">Warte auf Spielstart…</div>
|
||||
<div class="wait-sub" id="sub">Der Host startet das Spiel in Kürze. Diese Seite aktualisiert sich automatisch.</div>
|
||||
<div class="message" id="message" style="display:none;"></div>
|
||||
<button class="secondary" onclick="abbrechen()">Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/js/sidebar.js"></script>
|
||||
<script>
|
||||
const params = new URLSearchParams(location.search);
|
||||
const einladungId = params.get('id');
|
||||
if (!einladungId) window.location.replace('/userhome.html');
|
||||
|
||||
let pollInterval = null;
|
||||
|
||||
async function pruefen() {
|
||||
try {
|
||||
const res = await fetch(`/bdsm/einladung/${einladungId}`);
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
|
||||
if (data.status === 'CANCELLED') {
|
||||
stopPoll();
|
||||
zeigeFehler('Die Einladung wurde abgebrochen.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.sessionId) {
|
||||
stopPoll();
|
||||
// Meinen Mitspieler laden
|
||||
try {
|
||||
const mRes = await fetch(`/bdsm/${data.sessionId}/mitspieler/me`);
|
||||
if (mRes.ok) {
|
||||
const mData = await mRes.json();
|
||||
sessionStorage.setItem('bdsm-guest-mitspieler-id', mData.mitspielerId);
|
||||
sessionStorage.setItem('bdsm-guest-name', mData.name);
|
||||
}
|
||||
} catch (_) {}
|
||||
sessionStorage.setItem('bdsm-session-id', data.sessionId);
|
||||
sessionStorage.setItem('bdsm-is-guest', 'true');
|
||||
window.location.replace('/bdsmingame.html');
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function stopPoll() {
|
||||
if (pollInterval) { clearInterval(pollInterval); pollInterval = null; }
|
||||
}
|
||||
|
||||
function zeigeFehler(text) {
|
||||
document.getElementById('sub').style.display = 'none';
|
||||
const el = document.getElementById('message');
|
||||
el.textContent = text;
|
||||
el.className = 'message error';
|
||||
el.style.display = '';
|
||||
}
|
||||
|
||||
async function abbrechen() {
|
||||
stopPoll();
|
||||
await fetch(`/bdsm/einladung/${einladungId}`, { method: 'DELETE' }).catch(() => {});
|
||||
window.location.href = '/userhome.html';
|
||||
}
|
||||
|
||||
// Sofort prüfen, dann alle 3 Sekunden
|
||||
pruefen();
|
||||
pollInterval = setInterval(pruefen, 3000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -406,11 +406,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs: Feed | Pinnwand | Lock-Historie -->
|
||||
<!-- Tabs: Feed | Pinnwand | Spielhistorie -->
|
||||
<div class="profil-tabs" style="margin-top:1.25rem;">
|
||||
<button class="profil-tab-btn active" id="tabBtnPosts" onclick="switchProfilTab('posts', this)">Feed</button>
|
||||
<button class="profil-tab-btn" id="tabBtnPinnwand" onclick="switchProfilTab('pinnwand', this)">Pinnwand</button>
|
||||
<button class="profil-tab-btn" id="tabBtnLockHistory" onclick="switchProfilTab('lockhistory', this); loadLockHistory()">Lock-Historie</button>
|
||||
<button class="profil-tab-btn" id="tabBtnGameHistory" onclick="switchProfilTab('gamehistory', this); loadGameHistory()">Spielhistorie</button>
|
||||
</div>
|
||||
|
||||
<!-- Feed Tab (vorausgewählt) -->
|
||||
@@ -429,10 +429,10 @@
|
||||
<div id="pinnwandList"></div>
|
||||
</div>
|
||||
|
||||
<!-- Lock-Historie Tab -->
|
||||
<div class="profil-tab-panel" id="tab-lockhistory">
|
||||
<div id="lockHistoryList" style="margin-top:0.75rem;"></div>
|
||||
<p id="lockHistoryEmpty" style="color:var(--color-muted);font-size:0.9rem;display:none;">Keine abgeschlossenen Locks vorhanden.</p>
|
||||
<!-- Spielhistorie Tab -->
|
||||
<div class="profil-tab-panel" id="tab-gamehistory">
|
||||
<div id="gameHistoryList" style="margin-top:0.75rem;"></div>
|
||||
<p id="gameHistoryEmpty" style="color:var(--color-muted);font-size:0.9rem;display:none;">Keine abgeschlossenen Locks vorhanden.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -467,6 +467,7 @@
|
||||
// ── State ──
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
let targetUserId = params.get('userId');
|
||||
const previewMode = params.get('preview'); // 'FREUND' | 'UNBEKANNT' | null
|
||||
let myUserId = null;
|
||||
let isOwnProfile = false;
|
||||
let profileData = null;
|
||||
@@ -524,25 +525,52 @@
|
||||
}
|
||||
|
||||
myUserId = me ? me.userId : null;
|
||||
isOwnProfile = me && me.userId === profile.userId;
|
||||
isOwnProfile = !previewMode && me && me.userId === profile.userId;
|
||||
profileData = profile;
|
||||
allImages = images;
|
||||
|
||||
// ── Preview-Modus: friendStatus simulieren ──
|
||||
if (previewMode) {
|
||||
profile.friendStatus = (previewMode === 'FREUND') ? 'FRIEND' : 'NONE';
|
||||
showPreviewBanner(previewMode);
|
||||
}
|
||||
|
||||
const isFriend = profile.friendStatus === 'FRIEND';
|
||||
|
||||
document.title = profile.name + ' – XXX The Game';
|
||||
renderHeader(profile);
|
||||
renderGallery();
|
||||
loadFriends();
|
||||
if (profile.beschreibung) {
|
||||
|
||||
// ── Galerie ──
|
||||
if (canSee(profile.sichtbarkeitGalerie, isFriend, isOwnProfile)) {
|
||||
renderGallery();
|
||||
}
|
||||
|
||||
// ── Freunde ──
|
||||
if (canSee(profile.sichtbarkeitFreunde, isFriend, isOwnProfile)) {
|
||||
loadFriends();
|
||||
}
|
||||
|
||||
if (profile.beschreibung && canSee(profile.sichtbarkeitGrunddaten, isFriend, isOwnProfile)) {
|
||||
document.getElementById('beschreibungLabel').style.display = '';
|
||||
const el = document.getElementById('profilBeschreibung');
|
||||
el.style.display = '';
|
||||
el.textContent = profile.beschreibung;
|
||||
}
|
||||
await loadPinnwand();
|
||||
|
||||
// ── Tabs: Feed, Pinnwand, Spielhistorie ──
|
||||
applyTabPrivacy(profile, isFriend);
|
||||
|
||||
if (canSee(profile.sichtbarkeitPinnwand, isFriend, isOwnProfile)) {
|
||||
await loadPinnwand();
|
||||
}
|
||||
|
||||
document.getElementById('profileView').style.display = '';
|
||||
// Feed-Tab ist vorausgewählt → sofort laden
|
||||
loadProfilPosts();
|
||||
profilPostsObserver.observe(document.getElementById('profilPostsSentinel'));
|
||||
|
||||
// Feed-Tab ist vorausgewählt → sofort laden (nur wenn sichtbar)
|
||||
if (canSee(profile.sichtbarkeitFeed, isFriend, isOwnProfile)) {
|
||||
loadProfilPosts();
|
||||
profilPostsObserver.observe(document.getElementById('profilPostsSentinel'));
|
||||
}
|
||||
} catch {
|
||||
document.getElementById('loadingHint').textContent = 'Fehler beim Laden.';
|
||||
document.getElementById('loadingHint').style.display = '';
|
||||
@@ -568,14 +596,16 @@
|
||||
d.innerHTML = `<span class="label">${label}</span><span class="value">${esc(value)}</span>`;
|
||||
tags.appendChild(d);
|
||||
};
|
||||
if (profile.alter) addTag('Alter', profile.alter + ' J.');
|
||||
if (profile.groesse) addTag('Größe', profile.groesse + ' cm');
|
||||
if (profile.gewicht) addTag('Gewicht', profile.gewicht + ' kg');
|
||||
if (profile.geschlecht) addTag('Geschlecht', GESCHLECHT_LABEL[profile.geschlecht] || profile.geschlecht);
|
||||
if (profile.neigung) addTag('Neigung', NEIGUNG_LABEL[profile.neigung] || profile.neigung);
|
||||
if (profile.beziehungsstatus) addTag('Beziehung', BEZIEHUNG_LABEL[profile.beziehungsstatus] || profile.beziehungsstatus);
|
||||
if (profile.lockeeXp > 0) addTag('🔒 Lockee XP', profile.lockeeXp + ' XP');
|
||||
if (profile.keyholderXp > 0) addTag('🔑 Keyholder XP', profile.keyholderXp + ' XP');
|
||||
const grunddatenVisible = canSee(profile.sichtbarkeitGrunddaten, profile.friendStatus === 'FRIEND', isOwnProfile);
|
||||
if (grunddatenVisible && profile.alter) addTag('Alter', profile.alter + ' J.');
|
||||
if (grunddatenVisible && profile.groesse) addTag('Größe', profile.groesse + ' cm');
|
||||
if (grunddatenVisible && profile.gewicht) addTag('Gewicht', profile.gewicht + ' kg');
|
||||
if (grunddatenVisible && profile.geschlecht) addTag('Geschlecht', GESCHLECHT_LABEL[profile.geschlecht] || profile.geschlecht);
|
||||
if (grunddatenVisible && profile.neigung) addTag('Neigung', NEIGUNG_LABEL[profile.neigung] || profile.neigung);
|
||||
if (grunddatenVisible && profile.beziehungsstatus) addTag('Beziehung', BEZIEHUNG_LABEL[profile.beziehungsstatus] || profile.beziehungsstatus);
|
||||
const xpVisible = canSee(profile.sichtbarkeitXp, profile.friendStatus === 'FRIEND', isOwnProfile);
|
||||
if (xpVisible && profile.lockeeXp > 0) addTag('🔒 Lockee XP', profile.lockeeXp + ' XP');
|
||||
if (xpVisible && profile.keyholderXp > 0) addTag('🔑 Keyholder XP', profile.keyholderXp + ' XP');
|
||||
|
||||
// Action buttons
|
||||
const actions = document.getElementById('profileActions');
|
||||
@@ -596,6 +626,50 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ── Privacy helpers ──
|
||||
function canSee(sichtbarkeit, isFriend, isOwn) {
|
||||
if (isOwn || !sichtbarkeit) return true;
|
||||
if (sichtbarkeit === 'ALLE') return true;
|
||||
if (sichtbarkeit === 'NUR_FREUNDE') return isFriend;
|
||||
// NUR_ICH
|
||||
return false;
|
||||
}
|
||||
|
||||
function applyTabPrivacy(profile, isFriend) {
|
||||
const showFeed = canSee(profile.sichtbarkeitFeed, isFriend, isOwnProfile);
|
||||
const showPinnwand = canSee(profile.sichtbarkeitPinnwand, isFriend, isOwnProfile);
|
||||
const showHistory = canSee(profile.sichtbarkeitLockhistorie, isFriend, isOwnProfile);
|
||||
|
||||
const btnFeed = document.getElementById('tabBtnPosts');
|
||||
const btnPinnwand = document.getElementById('tabBtnPinnwand');
|
||||
const btnHistory = document.getElementById('tabBtnGameHistory');
|
||||
|
||||
if (!showFeed) { btnFeed.style.display = 'none'; document.getElementById('tab-posts').classList.remove('active'); }
|
||||
if (!showPinnwand) { btnPinnwand.style.display = 'none'; }
|
||||
if (!showHistory) { btnHistory.style.display = 'none'; }
|
||||
|
||||
// Ersten sichtbaren Tab aktivieren
|
||||
if (!showFeed) {
|
||||
btnFeed.classList.remove('active');
|
||||
document.getElementById('tab-posts').classList.remove('active');
|
||||
if (showPinnwand) {
|
||||
btnPinnwand.classList.add('active');
|
||||
document.getElementById('tab-pinnwand').classList.add('active');
|
||||
} else if (showHistory) {
|
||||
btnHistory.classList.add('active');
|
||||
document.getElementById('tab-gamehistory').classList.add('active');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showPreviewBanner(mode) {
|
||||
const banner = document.createElement('div');
|
||||
banner.style.cssText = 'background:var(--color-secondary);border:1px solid var(--color-primary);border-radius:8px;padding:0.65rem 1rem;margin-bottom:1rem;font-size:0.88rem;display:flex;align-items:center;justify-content:space-between;gap:0.75rem;';
|
||||
const label = mode === 'FREUND' ? '👥 Vorschau aus Freundessicht' : '👤 Vorschau aus Sicht einer fremden Person';
|
||||
banner.innerHTML = `<span>${label}</span><a href="/einstellungen.html" style="font-size:0.82rem;color:var(--color-primary);">← Einstellungen</a>`;
|
||||
document.getElementById('profileView').prepend(banner);
|
||||
}
|
||||
|
||||
// ── Tab switching ──
|
||||
function switchProfilTab(name, btn) {
|
||||
document.querySelectorAll('.profil-tab-btn').forEach(b => b.classList.remove('active'));
|
||||
@@ -777,47 +851,60 @@
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// ── Lock-Historie ──
|
||||
let lockHistoryLoaded = false;
|
||||
async function loadLockHistory() {
|
||||
if (lockHistoryLoaded) return;
|
||||
lockHistoryLoaded = true;
|
||||
const list = document.getElementById('lockHistoryList');
|
||||
const empty = document.getElementById('lockHistoryEmpty');
|
||||
// ── Spielhistorie ──
|
||||
const GAME_TYPE_ICON = {
|
||||
CARDLOCK: '<span style="position:relative;display:inline-block;line-height:1;"><img src="/img/card.png" style="width:2.7rem;height:2.7rem;object-fit:contain;display:block;"><span style="position:absolute;bottom:-2px;right:-4px;font-size:1.3rem;line-height:1;">🔒</span></span>',
|
||||
TIMELOCK: '<span style="position:relative;display:inline-block;line-height:1;"><span style="font-size:2.7rem;">⏰</span><span style="position:absolute;bottom:-2px;right:-4px;font-size:1.3rem;line-height:1;">🔒</span></span>',
|
||||
BDSM: '⛓️',
|
||||
VANILLA: '❤️'
|
||||
};
|
||||
const ROLE_BADGE = { KEYHOLDER: '🔑', LOCKEE: '🔒', PLAYER: '' };
|
||||
|
||||
let gameHistoryLoaded = false;
|
||||
async function loadGameHistory() {
|
||||
if (gameHistoryLoaded) return;
|
||||
gameHistoryLoaded = true;
|
||||
const list = document.getElementById('gameHistoryList');
|
||||
const empty = document.getElementById('gameHistoryEmpty');
|
||||
list.innerHTML = '<p style="color:var(--color-muted);font-size:0.85rem;">Lädt…</p>';
|
||||
try {
|
||||
const res = await fetch('/lockhistory?userId=' + targetUserId);
|
||||
const res = await fetch('/gamehistory?userId=' + targetUserId);
|
||||
if (!res.ok) { list.innerHTML = ''; return; }
|
||||
const entries = await res.json();
|
||||
list.innerHTML = '';
|
||||
if (entries.length === 0) { empty.style.display = ''; return; }
|
||||
|
||||
list.innerHTML = entries.map(e => {
|
||||
const icon = e.role === 'KEYHOLDER' ? '🔑' : '🔒';
|
||||
const partner = e.role === 'KEYHOLDER'
|
||||
? (e.lockeeName ? `<span style="color:var(--color-muted);font-size:0.78rem;">Lockee: ${esc(e.lockeeName)}</span>` : '')
|
||||
: (e.keyholderName ? `<span style="color:var(--color-muted);font-size:0.78rem;">Keyholder: ${esc(e.keyholderName)}</span>` : '<span style="color:var(--color-muted);font-size:0.78rem;">Self-Lock</span>');
|
||||
const days = Math.floor(e.durationMinutes / 1440);
|
||||
const hours = Math.floor((e.durationMinutes % 1440) / 60);
|
||||
const mins = e.durationMinutes % 60;
|
||||
const dur = days > 0
|
||||
const gameIconRaw = GAME_TYPE_ICON[e.gameType] || '🎮';
|
||||
const gameIcon = (e.gameType === 'CARDLOCK' || e.gameType === 'TIMELOCK')
|
||||
? gameIconRaw
|
||||
: `<span style="font-size:2.7rem;line-height:1;">${gameIconRaw}</span>`;
|
||||
|
||||
const days = Math.floor(e.durationMinutes / 1440);
|
||||
const hours = Math.floor((e.durationMinutes % 1440) / 60);
|
||||
const mins = e.durationMinutes % 60;
|
||||
const dur = days > 0
|
||||
? `${days}d ${hours}h ${mins}min`
|
||||
: hours > 0 ? `${hours}h ${mins}min` : `${mins}min`;
|
||||
const avatar = e.partnerPic
|
||||
? `<img src="data:image/jpeg;base64,${e.partnerPic}" style="width:40px;height:40px;border-radius:50%;object-fit:cover;display:block;">`
|
||||
: `<div style="width:40px;height:40px;border-radius:50%;background:var(--color-secondary);display:flex;align-items:center;justify-content:center;font-size:1.1rem;">👤</div>`;
|
||||
return `<div style="display:flex;align-items:center;gap:0.75rem;padding:0.65rem 0;border-bottom:1px solid var(--color-secondary);">
|
||||
<div style="position:relative;width:40px;height:40px;flex-shrink:0;">
|
||||
${avatar}
|
||||
<span style="position:absolute;top:-3px;left:-3px;font-size:1.4rem;line-height:1;">${icon}</span>
|
||||
</div>
|
||||
|
||||
const participants = (e.participants || []).map(p => {
|
||||
const badge = ROLE_BADGE[p.role] || '';
|
||||
const img = p.picture
|
||||
? `<img src="data:image/png;base64,${p.picture}" style="width:40px;height:40px;border-radius:50%;object-fit:cover;display:block;">`
|
||||
: `<div style="width:40px;height:40px;border-radius:50%;background:var(--color-secondary);display:flex;align-items:center;justify-content:center;font-size:1.1rem;flex-shrink:0;">👤</div>`;
|
||||
return `<a href="/benutzer.html?userId=${esc(p.userId)}" style="position:relative;flex-shrink:0;text-decoration:none;" title="${esc(p.name || '')} (${p.role})">
|
||||
${img}
|
||||
${badge ? `<span style="position:absolute;top:-4px;right:-4px;font-size:0.9rem;line-height:1;">${badge}</span>` : ''}
|
||||
</a>`;
|
||||
}).join('');
|
||||
|
||||
return `<div style="display:flex;align-items:flex-start;gap:0.85rem;padding:0.75rem 0;border-bottom:1px solid var(--color-secondary);">
|
||||
<div style="flex-shrink:0;width:3rem;text-align:center;">${gameIcon}</div>
|
||||
<div style="flex:1;min-width:0;">
|
||||
<div style="font-weight:600;font-size:0.92rem;">${esc(e.lockName) || 'Unbenanntes Lock'}</div>
|
||||
<div style="display:flex;gap:1rem;flex-wrap:wrap;margin-top:0.15rem;">
|
||||
${partner}
|
||||
<span style="color:var(--color-muted);font-size:0.78rem;">⏱ ${dur}</span>
|
||||
<span style="color:var(--color-muted);font-size:0.78rem;">${fmtDate(e.unlockTime)}</span>
|
||||
</div>
|
||||
<div style="font-weight:600;font-size:0.92rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${esc(e.gameName) || 'Unbenannt'}</div>
|
||||
<div style="font-size:0.78rem;color:var(--color-muted);margin-top:0.15rem;">⏱ ${dur} · ${new Date(e.endTime).toLocaleDateString('de-DE', {day:'2-digit',month:'2-digit',year:'numeric'})}</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:0.35rem;align-items:center;flex-shrink:0;">${participants}</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
} catch(e) { list.innerHTML = ''; }
|
||||
@@ -954,7 +1041,7 @@
|
||||
: '◉';
|
||||
const bildRaw = bilderCarousel(p.bilder);
|
||||
const bildHtml = bildRaw
|
||||
? `<div class="post-bild-wrap" data-post-id="${p.postId}">${bildRaw}<div class="post-bild-hover-icon">💬</div></div>`
|
||||
? `<div class="post-bild-wrap" data-post-id="${p.postId}">${bildRaw}</div>`
|
||||
: '';
|
||||
const privacyLabel = p.isPublic ? '' : '<span style="font-size:0.72rem;color:var(--color-muted);margin-left:0.4rem;">🔒 privat</span>';
|
||||
|
||||
|
||||
@@ -167,6 +167,33 @@
|
||||
}
|
||||
.blind-hint-icon { font-size: 1.4rem; flex-shrink: 0; }
|
||||
|
||||
/* Bestätigungs-Modal */
|
||||
.confirm-modal-bg {
|
||||
display: none; position: fixed; inset: 0; z-index: 600;
|
||||
align-items: center; justify-content: center;
|
||||
}
|
||||
.confirm-modal-bg.open { display: flex; }
|
||||
.confirm-modal-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.6); }
|
||||
.confirm-modal-box {
|
||||
position: relative; background: var(--color-card);
|
||||
border: 1px solid var(--color-secondary); border-radius: 12px;
|
||||
padding: 1.75rem 1.5rem 1.5rem; max-width: 380px; width: 92%; z-index: 1;
|
||||
display: flex; flex-direction: column; gap: 1rem;
|
||||
}
|
||||
.confirm-modal-title {
|
||||
font-weight: 700; font-size: 1rem; padding-right: 1.5rem;
|
||||
}
|
||||
.confirm-modal-text {
|
||||
font-size: 0.9rem; color: var(--color-muted); line-height: 1.5;
|
||||
}
|
||||
.confirm-modal-actions {
|
||||
display: flex; gap: 0.6rem; justify-content: flex-end; margin-top: 0.25rem;
|
||||
}
|
||||
.confirm-modal-actions button { width: auto; padding: 0.6rem 1.3rem; font-size: 0.9rem; }
|
||||
.confirm-modal-cancel { background: var(--color-secondary) !important; color: var(--color-text) !important; }
|
||||
.confirm-modal-ok { background: #c0392b !important; }
|
||||
.confirm-modal-ok:hover { background: #a93226 !important; }
|
||||
|
||||
/* Entsperrcode-Modal */
|
||||
.unlock-modal-bg {
|
||||
display: none; position: fixed; inset: 0; z-index: 500;
|
||||
@@ -214,6 +241,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bestätigungs-Modal -->
|
||||
<div class="confirm-modal-bg" id="confirmModal">
|
||||
<div class="confirm-modal-overlay" onclick="confirmCancel()"></div>
|
||||
<div class="confirm-modal-box">
|
||||
<button onclick="confirmCancel()" style="position:absolute;top:0.75rem;right:0.75rem;background:none;border:none;color:var(--color-muted);font-size:1.3rem;cursor:pointer;padding:0.2rem 0.5rem;line-height:1;" title="Schließen">✕</button>
|
||||
<div class="confirm-modal-title" id="confirmTitle"></div>
|
||||
<div class="confirm-modal-text" id="confirmText"></div>
|
||||
<div class="confirm-modal-actions">
|
||||
<button class="confirm-modal-cancel" onclick="confirmCancel()">Abbrechen</button>
|
||||
<button class="confirm-modal-ok" id="confirmOkBtn">Bestätigen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lockee-Einladungs-Dialog -->
|
||||
<div class="lockee-dialog-bg" id="lockeeInviteDialog">
|
||||
<div class="lockee-dialog-overlay" onclick="closeLockeeInviteDialog()"></div>
|
||||
@@ -475,9 +516,29 @@
|
||||
renderSentPage();
|
||||
}
|
||||
|
||||
// ── Bestätigungs-Modal ──
|
||||
let _confirmResolve = null;
|
||||
|
||||
function showConfirm(title, text) {
|
||||
document.getElementById('confirmTitle').textContent = title;
|
||||
document.getElementById('confirmText').textContent = text;
|
||||
document.getElementById('confirmModal').classList.add('open');
|
||||
return new Promise(resolve => {
|
||||
_confirmResolve = resolve;
|
||||
document.getElementById('confirmOkBtn').onclick = () => { confirmClose(true); };
|
||||
});
|
||||
}
|
||||
|
||||
function confirmCancel() { confirmClose(false); }
|
||||
|
||||
function confirmClose(result) {
|
||||
document.getElementById('confirmModal').classList.remove('open');
|
||||
if (_confirmResolve) { _confirmResolve(result); _confirmResolve = null; }
|
||||
}
|
||||
|
||||
// ── Aktionen: Empfangen ──
|
||||
async function declineLockeeInvitation(token, btn) {
|
||||
if (!confirm('Bist du sicher, dass du diese Einladung ablehnen möchtest?')) return;
|
||||
if (!await showConfirm('Einladung ablehnen', 'Bist du sicher, dass du diese Einladung ablehnen möchtest?')) return;
|
||||
btn.disabled = true;
|
||||
try {
|
||||
const res = await fetch('/lockee/invitation/' + encodeURIComponent(token), { method: 'DELETE' });
|
||||
@@ -487,7 +548,7 @@
|
||||
}
|
||||
|
||||
async function declineKhInvitation(token, btn) {
|
||||
if (!confirm('Bist du sicher, dass du diese Einladung ablehnen möchtest?')) return;
|
||||
if (!await showConfirm('Einladung ablehnen', 'Bist du sicher, dass du diese Einladung ablehnen möchtest?')) return;
|
||||
btn.disabled = true;
|
||||
try {
|
||||
const res = await fetch('/keyholder/invitations/mine/' + encodeURIComponent(token), { method: 'DELETE' });
|
||||
@@ -498,10 +559,11 @@
|
||||
|
||||
// ── Aktionen: Gesendet ──
|
||||
async function cancelSentInvitation(token, type, btn) {
|
||||
const msg = type === 'lockee'
|
||||
? 'Einladung zurückziehen? Das Lock wird gelöscht und der Lockee wird benachrichtigt.'
|
||||
: 'Keyholder-Einladung zurückziehen? Der Keyholder wird benachrichtigt.';
|
||||
if (!confirm(msg)) return;
|
||||
const title = 'Einladung zurückziehen';
|
||||
const text = type === 'lockee'
|
||||
? 'Das Lock wird gelöscht und der Lockee wird benachrichtigt.'
|
||||
: 'Der Keyholder wird benachrichtigt.';
|
||||
if (!await showConfirm(title, text)) return;
|
||||
btn.disabled = true;
|
||||
const url = type === 'lockee'
|
||||
? '/lockee/invitations/sent/' + encodeURIComponent(token)
|
||||
@@ -634,7 +696,7 @@
|
||||
|
||||
async function declineLockeeInviteDialog() {
|
||||
if (!activeDialogToken) return;
|
||||
if (!confirm('Bist du sicher, dass du diese Einladung ablehnen möchtest? Das Lock wird gelöscht.')) return;
|
||||
if (!await showConfirm('Einladung ablehnen', 'Bist du sicher, dass du diese Einladung ablehnen möchtest? Das Lock wird gelöscht.')) return;
|
||||
const declineBtn = document.querySelector('.btn-decline');
|
||||
declineBtn.disabled = true;
|
||||
try {
|
||||
|
||||
968
xxxthegame/src/main/resources/static/einstellungen.html
Normal file
968
xxxthegame/src/main/resources/static/einstellungen.html
Normal file
@@ -0,0 +1,968 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Einstellungen – XXX The Game</title>
|
||||
<link rel="stylesheet" href="/css/variables.css">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<style>
|
||||
.settings-page h1 {
|
||||
font-size: 1.6rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
/* ── Accordion ── */
|
||||
.settings-section {
|
||||
background: var(--color-card);
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-radius: 10px;
|
||||
margin-bottom: 0.75rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.settings-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
padding: 1rem 1.25rem;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.settings-section-header:hover {
|
||||
background: rgba(255,255,255,0.03);
|
||||
}
|
||||
|
||||
.settings-section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.settings-section-arrow {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-muted);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.settings-section.open .settings-section-arrow {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.settings-section-body {
|
||||
display: none;
|
||||
padding: 0 1.25rem 1.25rem;
|
||||
border-top: 1px solid var(--color-secondary);
|
||||
}
|
||||
|
||||
.settings-section.open .settings-section-body {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ── Einstellungs-Zeilen ── */
|
||||
.settings-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 0.9rem 0;
|
||||
}
|
||||
|
||||
.settings-row-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.settings-row-label {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.15rem;
|
||||
}
|
||||
|
||||
.settings-row-desc {
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.settings-row select {
|
||||
width: auto;
|
||||
min-width: 150px;
|
||||
padding: 0.45rem 0.75rem;
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* ── Benachrichtigungen Grid ── */
|
||||
.notif-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 5rem 5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.notif-grid-row {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.notif-grid-row > * {
|
||||
padding: 0.75rem 0;
|
||||
}
|
||||
|
||||
.notif-grid-row.notif-header > * {
|
||||
padding-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.notif-col-check {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.notif-col-check input[type="checkbox"] {
|
||||
width: 1.1rem;
|
||||
height: 1.1rem;
|
||||
accent-color: var(--color-primary, #c0392b);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ── Speichern-Feedback (Toast) ── */
|
||||
.save-toast {
|
||||
position: fixed;
|
||||
bottom: 1.5rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(2rem);
|
||||
background: var(--color-card);
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-left: 3px solid var(--color-success, #4caf50);
|
||||
border-radius: 8px;
|
||||
padding: 0.6rem 1.25rem;
|
||||
font-size: 0.88rem;
|
||||
color: var(--color-success, #4caf50);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s, transform 0.2s;
|
||||
z-index: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.save-toast.visible {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
|
||||
/* ── Spiel-Einstellungen Check-Items ── */
|
||||
.spiel-check-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
margin-top: 0.35rem;
|
||||
}
|
||||
.spiel-check-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
background: var(--color-secondary);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
padding: 0.35rem 0.65rem;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s;
|
||||
user-select: none;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
.spiel-check-item.is-checked { border-color: var(--color-primary); }
|
||||
.spiel-check-item input {
|
||||
accent-color: var(--color-primary);
|
||||
width: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
.spiel-subsection {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.spiel-subsection-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.4rem;
|
||||
border-bottom: 1px solid var(--color-secondary);
|
||||
}
|
||||
.spiel-field { margin-bottom: 1rem; }
|
||||
.spiel-field > .settings-row-label { margin-bottom: 0.2rem; }
|
||||
|
||||
/* ── Modal ── */
|
||||
.modal-backdrop {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0,0,0,0.6);
|
||||
z-index: 200;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-backdrop.visible { display: flex; }
|
||||
|
||||
.modal {
|
||||
background: var(--color-card);
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.6);
|
||||
}
|
||||
|
||||
.modal h2 {
|
||||
color: var(--color-primary);
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.modal-actions button { flex: 1; margin-top: 0; }
|
||||
|
||||
/* ── Separator ── */
|
||||
.settings-separator {
|
||||
border: none;
|
||||
border-top: 1px solid var(--color-secondary);
|
||||
margin: 1.25rem 0 0;
|
||||
}
|
||||
|
||||
/* ── Vorschau-Buttons ── */
|
||||
.preview-row {
|
||||
padding-top: 1.25rem;
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.preview-row span {
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-muted);
|
||||
flex-basis: 100%;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.preview-row button {
|
||||
width: auto;
|
||||
padding: 0.5rem 1.1rem;
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="app">
|
||||
<div class="main">
|
||||
<div class="content">
|
||||
<h1>⚙️ Einstellungen</h1>
|
||||
|
||||
<!-- Grunddaten -->
|
||||
<div class="settings-section" id="sec-grunddaten">
|
||||
<div class="settings-section-header" onclick="toggleSection('sec-grunddaten')">
|
||||
<span class="settings-section-title">👤 Grunddaten</span>
|
||||
<span class="settings-section-arrow">▸</span>
|
||||
</div>
|
||||
<div class="settings-section-body">
|
||||
|
||||
<div class="settings-row">
|
||||
<div class="settings-row-info">
|
||||
<div class="settings-row-label">Nickname</div>
|
||||
<div class="settings-row-desc" id="gd-name">…</div>
|
||||
</div>
|
||||
<button onclick="openNameDialog()" style="width:auto;padding:0.45rem 1rem;margin:0;font-size:0.9rem;">Ändern</button>
|
||||
</div>
|
||||
|
||||
<div class="settings-row">
|
||||
<div class="settings-row-info">
|
||||
<div class="settings-row-label">E-Mail</div>
|
||||
<div class="settings-row-desc" id="gd-email">…</div>
|
||||
</div>
|
||||
<button onclick="openEmailDialog()" style="width:auto;padding:0.45rem 1rem;margin:0;font-size:0.9rem;">Ändern</button>
|
||||
</div>
|
||||
|
||||
<div class="settings-row">
|
||||
<div class="settings-row-info">
|
||||
<div class="settings-row-label">Geburtsdatum</div>
|
||||
<div class="settings-row-desc" id="gd-geburtsdatum">…</div>
|
||||
</div>
|
||||
<button onclick="openGeburtsdatumDialog()" style="width:auto;padding:0.45rem 1rem;margin:0;font-size:0.9rem;">Ändern</button>
|
||||
</div>
|
||||
|
||||
<div class="settings-row">
|
||||
<div class="settings-row-info">
|
||||
<div class="settings-row-label" style="color:var(--color-primary);">Konto löschen</div>
|
||||
<div class="settings-row-desc">Alle Daten werden unwiderruflich gelöscht</div>
|
||||
</div>
|
||||
<button onclick="openDeleteDialog()" style="width:auto;padding:0.45rem 1rem;margin:0;font-size:0.9rem;background:transparent;border:1px solid var(--color-secondary);color:var(--color-muted);">Löschen</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Spiel Einstellungen -->
|
||||
<div class="settings-section" id="sec-spiel">
|
||||
<div class="settings-section-header" onclick="toggleSection('sec-spiel')">
|
||||
<span class="settings-section-title">🕹️ Spiel Einstellungen</span>
|
||||
<span class="settings-section-arrow">▸</span>
|
||||
</div>
|
||||
<div class="settings-section-body">
|
||||
|
||||
<!-- BDSM Game -->
|
||||
<div class="spiel-subsection">
|
||||
<div class="spiel-subsection-title">BDSM Game</div>
|
||||
|
||||
<div class="spiel-field">
|
||||
<div class="settings-row-label">Spiele mit Geschlecht</div>
|
||||
<div class="spiel-check-group" id="bdsm-spieltmit">
|
||||
<label class="spiel-check-item"><input type="checkbox" value="WEIBLICH"> Weiblich</label>
|
||||
<label class="spiel-check-item"><input type="checkbox" value="DIVERS"> Divers</label>
|
||||
<label class="spiel-check-item"><input type="checkbox" value="MAENNLICH"> Männlich</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="spiel-field">
|
||||
<div class="settings-row-label">Meine Rollen</div>
|
||||
<div class="spiel-check-group" id="bdsm-rollen">
|
||||
<label class="spiel-check-item"><input type="checkbox" value="AUFGABE_AKTIV"> Aufgaben aktiv</label>
|
||||
<label class="spiel-check-item"><input type="checkbox" value="AUFGABE_PASSIV"> Aufgaben passiv</label>
|
||||
<label class="spiel-check-item"><input type="checkbox" value="BESTRAFUNG_AKTIV"> Bestrafungen aktiv</label>
|
||||
<label class="spiel-check-item"><input type="checkbox" value="BESTRAFUNG_PASSIV"> Bestrafungen passiv</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="spiel-field">
|
||||
<div class="settings-row-label">Was ich einsetze</div>
|
||||
<div class="spiel-check-group" id="bdsm-werkzeuge">
|
||||
<label class="spiel-check-item"><input type="checkbox" value="MUND"> Mund</label>
|
||||
<label class="spiel-check-item"><input type="checkbox" value="VAGINA"> Vagina</label>
|
||||
<label class="spiel-check-item"><input type="checkbox" value="PENIS"> Penis</label>
|
||||
<label class="spiel-check-item"><input type="checkbox" value="ANUS"> Anus</label>
|
||||
<label class="spiel-check-item"><input type="checkbox" value="UMSCHNALLDILDO"> Umschnall-Dildo</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Benachrichtigungen -->
|
||||
<div class="settings-section" id="sec-benachrichtigungen">
|
||||
<div class="settings-section-header" onclick="toggleSection('sec-benachrichtigungen')">
|
||||
<span class="settings-section-title">🔔 Benachrichtigungen</span>
|
||||
<span class="settings-section-arrow">▸</span>
|
||||
</div>
|
||||
<div class="settings-section-body">
|
||||
<div class="notif-grid">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="notif-grid-row notif-header">
|
||||
<div></div>
|
||||
<div class="notif-col-check settings-row-label">In-App</div>
|
||||
<div class="notif-col-check settings-row-label">E-Mail</div>
|
||||
</div>
|
||||
|
||||
<!-- Einladungen -->
|
||||
<div class="notif-grid-row">
|
||||
<div>
|
||||
<div class="settings-row-label">Einladungen</div>
|
||||
<div class="settings-row-desc">Einladungen zu Locks und Spielen, Annahmen und Ablehnungen</div>
|
||||
</div>
|
||||
<div class="notif-col-check">
|
||||
<input type="checkbox" id="notif-INVITATION-inApp" onchange="saveNotifications()">
|
||||
</div>
|
||||
<div class="notif-col-check">
|
||||
<input type="checkbox" id="notif-INVITATION-email" onchange="saveNotifications()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Spielstatus -->
|
||||
<div class="notif-grid-row">
|
||||
<div>
|
||||
<div class="settings-row-label">Spielstatus</div>
|
||||
<div class="settings-row-desc">Karten, Aufgaben, Verifikationen, Einfrierungen und andere Spielereignisse</div>
|
||||
</div>
|
||||
<div class="notif-col-check">
|
||||
<input type="checkbox" id="notif-GAME_STATE-inApp" onchange="saveNotifications()">
|
||||
</div>
|
||||
<div class="notif-col-check">
|
||||
<input type="checkbox" id="notif-GAME_STATE-email" onchange="saveNotifications()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notfall -->
|
||||
<div class="notif-grid-row">
|
||||
<div>
|
||||
<div class="settings-row-label">Notfall</div>
|
||||
<div class="settings-row-desc">Notfall-Entsperrungen und dringende Meldungen</div>
|
||||
</div>
|
||||
<div class="notif-col-check">
|
||||
<input type="checkbox" id="notif-EMERGENCY-inApp" onchange="saveNotifications()">
|
||||
</div>
|
||||
<div class="notif-col-check">
|
||||
<input type="checkbox" id="notif-EMERGENCY-email" onchange="saveNotifications()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Freundschaftsanfragen -->
|
||||
<div class="notif-grid-row">
|
||||
<div>
|
||||
<div class="settings-row-label">Freundschaftsanfragen</div>
|
||||
<div class="settings-row-desc">Neue Freundschaftsanfragen von anderen Nutzern</div>
|
||||
</div>
|
||||
<div class="notif-col-check">
|
||||
<input type="checkbox" id="notif-FRIENDREQUEST-inApp" checked disabled
|
||||
title="In-App-Benachrichtigungen für Freundschaftsanfragen sind immer aktiv">
|
||||
</div>
|
||||
<div class="notif-col-check">
|
||||
<input type="checkbox" id="notif-FRIENDREQUEST-email" onchange="saveNotifications()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Datenschutz -->
|
||||
<div class="settings-section" id="sec-datenschutz">
|
||||
<div class="settings-section-header" onclick="toggleSection('sec-datenschutz')">
|
||||
<span class="settings-section-title">🛡️ Datenschutz</span>
|
||||
<span class="settings-section-arrow">▸</span>
|
||||
</div>
|
||||
<div class="settings-section-body">
|
||||
|
||||
<!-- Grunddaten -->
|
||||
<div class="settings-row">
|
||||
<div class="settings-row-info">
|
||||
<div class="settings-row-label">Grunddaten</div>
|
||||
<div class="settings-row-desc">Alter, Größe, Gewicht, Geschlecht, Neigung, Beziehungsstatus, Beschreibung</div>
|
||||
</div>
|
||||
<select id="sv-grunddaten" onchange="doSave()">
|
||||
<option value="ALLE">Alle</option>
|
||||
<option value="NUR_FREUNDE">Nur Freunde</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Galerie -->
|
||||
<div class="settings-row">
|
||||
<div class="settings-row-info">
|
||||
<div class="settings-row-label">Galerie</div>
|
||||
<div class="settings-row-desc">Fotos auf dem Profil</div>
|
||||
</div>
|
||||
<select id="sv-galerie" onchange="doSave()">
|
||||
<option value="ALLE">Alle</option>
|
||||
<option value="NUR_FREUNDE">Nur Freunde</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Freunde -->
|
||||
<div class="settings-row">
|
||||
<div class="settings-row-info">
|
||||
<div class="settings-row-label">Freundesliste</div>
|
||||
<div class="settings-row-desc">Wer kann sehen, wer deine Freunde sind</div>
|
||||
</div>
|
||||
<select id="sv-freunde" onchange="doSave()">
|
||||
<option value="ALLE">Alle</option>
|
||||
<option value="NUR_FREUNDE">Nur Freunde</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Feed -->
|
||||
<div class="settings-row">
|
||||
<div class="settings-row-info">
|
||||
<div class="settings-row-label">Feed / Posts</div>
|
||||
<div class="settings-row-desc">Posts auf dem Profil-Tab</div>
|
||||
</div>
|
||||
<select id="sv-feed" onchange="doSave()">
|
||||
<option value="ALLE">Alle</option>
|
||||
<option value="NUR_FREUNDE">Nur Freunde</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Pinnwand -->
|
||||
<div class="settings-row">
|
||||
<div class="settings-row-info">
|
||||
<div class="settings-row-label">Pinnwand</div>
|
||||
<div class="settings-row-desc">Einträge auf der Pinnwand</div>
|
||||
</div>
|
||||
<select id="sv-pinnwand" onchange="doSave()">
|
||||
<option value="ALLE">Alle</option>
|
||||
<option value="NUR_FREUNDE">Nur Freunde</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- XP -->
|
||||
<div class="settings-row">
|
||||
<div class="settings-row-info">
|
||||
<div class="settings-row-label">XP-Punkte</div>
|
||||
<div class="settings-row-desc">Lockee-XP und Keyholder-XP</div>
|
||||
</div>
|
||||
<select id="sv-xp" onchange="doSave()">
|
||||
<option value="ALLE">Alle</option>
|
||||
<option value="NUR_FREUNDE">Nur Freunde</option>
|
||||
<option value="NUR_ICH">Nur ich</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Lock-Historie -->
|
||||
<div class="settings-row">
|
||||
<div class="settings-row-info">
|
||||
<div class="settings-row-label">Lock-Historie</div>
|
||||
<div class="settings-row-desc">Abgeschlossene Locks und Keyholder-Aktivitäten</div>
|
||||
</div>
|
||||
<select id="sv-lockhistorie" onchange="doSave()">
|
||||
<option value="ALLE">Alle</option>
|
||||
<option value="NUR_FREUNDE">Nur Freunde</option>
|
||||
<option value="NUR_ICH">Nur ich</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<hr class="settings-separator">
|
||||
|
||||
<!-- Vorschau -->
|
||||
<div class="preview-row">
|
||||
<span>Profil-Vorschau – wie sieht mein Profil für andere aus?</span>
|
||||
<button onclick="openPreview('FREUND')">👥 Vorschau als Freund</button>
|
||||
<button onclick="openPreview('UNBEKANNT')" style="background:var(--color-secondary);color:var(--color-text);">👤 Vorschau als Fremder</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nickname Modal -->
|
||||
<div class="modal-backdrop" id="nameModal">
|
||||
<div class="modal">
|
||||
<h2>Nickname ändern</h2>
|
||||
<label for="newName">Neuer Nickname</label>
|
||||
<input type="text" id="newName" placeholder="Neuer Name" autocomplete="off">
|
||||
<div class="message" id="nameMessage"></div>
|
||||
<div class="modal-actions">
|
||||
<button class="secondary" onclick="closeNameDialog()">Abbrechen</button>
|
||||
<button id="nameConfirmBtn" onclick="saveName()">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Geburtsdatum Modal -->
|
||||
<div class="modal-backdrop" id="geburtsdatumModal">
|
||||
<div class="modal">
|
||||
<h2>Geburtsdatum ändern</h2>
|
||||
<label for="newGeburtsdatum">Neues Geburtsdatum</label>
|
||||
<input type="date" id="newGeburtsdatum" autocomplete="bday">
|
||||
<div class="message" id="geburtsdatumMessage"></div>
|
||||
<div class="modal-actions">
|
||||
<button class="secondary" onclick="closeGeburtsdatumDialog()">Abbrechen</button>
|
||||
<button id="geburtsdatumConfirmBtn" onclick="saveGeburtsdatum()">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- E-Mail Modal -->
|
||||
<div class="modal-backdrop" id="emailModal">
|
||||
<div class="modal">
|
||||
<h2>E-Mail-Adresse ändern</h2>
|
||||
<label for="newEmail">Neue E-Mail-Adresse</label>
|
||||
<input type="email" id="newEmail" placeholder="neue@email.de" autocomplete="off">
|
||||
<div class="message" id="emailMessage"></div>
|
||||
<div class="modal-actions">
|
||||
<button class="secondary" onclick="closeEmailDialog()">Abbrechen</button>
|
||||
<button id="emailConfirmBtn" onclick="requestEmailChange()">Bestätigungsmail senden</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Konto löschen Modal -->
|
||||
<div class="modal-backdrop" id="deleteModal">
|
||||
<div class="modal">
|
||||
<h2>Konto löschen</h2>
|
||||
<p style="color:var(--color-muted);font-size:0.9rem;margin-bottom:0.75rem;">
|
||||
Dein Konto sowie alle gespeicherten Aufgaben und Toys werden unwiderruflich gelöscht.
|
||||
</p>
|
||||
<p style="color:var(--color-primary);font-size:0.85rem;">Dieser Vorgang kann nicht rückgängig gemacht werden.</p>
|
||||
<div class="message" id="deleteMessage"></div>
|
||||
<div class="modal-actions">
|
||||
<button class="secondary" onclick="closeDeleteDialog()">Abbrechen</button>
|
||||
<button id="deleteConfirmBtn" onclick="deleteAccount()" style="background:#c0392b;">Konto löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="save-toast" id="saveToast">✓ Gespeichert</div>
|
||||
|
||||
<script src="/js/sidebar.js"></script>
|
||||
<script src="/js/social-sidebar.js"></script>
|
||||
<script>
|
||||
let myUserId = null;
|
||||
let toastTimer = null;
|
||||
|
||||
function toggleSection(id) {
|
||||
const target = document.getElementById(id);
|
||||
const isOpen = target.classList.contains('open');
|
||||
document.querySelectorAll('.settings-section').forEach(s => s.classList.remove('open'));
|
||||
if (!isOpen) target.classList.add('open');
|
||||
}
|
||||
|
||||
// ── Grunddaten laden ──
|
||||
async function loadGrunddaten() {
|
||||
const res = await fetch('/login/me');
|
||||
if (res.status === 401) { window.location.href = '/login.html'; return; }
|
||||
if (!res.ok) return;
|
||||
const user = await res.json();
|
||||
myUserId = user.userId;
|
||||
document.getElementById('gd-name').textContent = user.name || '—';
|
||||
document.getElementById('gd-email').textContent = user.email || '—';
|
||||
document.getElementById('gd-geburtsdatum').textContent = formatGeburtsdatum(user.geburtsdatum);
|
||||
}
|
||||
|
||||
function formatGeburtsdatum(iso) {
|
||||
if (!iso) return '—';
|
||||
const [y, m, d] = iso.split('-');
|
||||
const today = new Date();
|
||||
const birth = new Date(iso);
|
||||
const age = today.getFullYear() - birth.getFullYear()
|
||||
- (today < new Date(today.getFullYear(), birth.getMonth(), birth.getDate()) ? 1 : 0);
|
||||
return `${d}.${m}.${y} (${age} Jahre)`;
|
||||
}
|
||||
|
||||
// ── Geburtsdatum Dialog ──
|
||||
function openGeburtsdatumDialog() {
|
||||
document.getElementById('newGeburtsdatum').value = '';
|
||||
hideModalMessage('geburtsdatumMessage');
|
||||
document.getElementById('geburtsdatumModal').classList.add('visible');
|
||||
document.getElementById('newGeburtsdatum').focus();
|
||||
}
|
||||
|
||||
function closeGeburtsdatumDialog() {
|
||||
document.getElementById('geburtsdatumModal').classList.remove('visible');
|
||||
}
|
||||
|
||||
async function saveGeburtsdatum() {
|
||||
const val = document.getElementById('newGeburtsdatum').value;
|
||||
if (!val) { showModalMessage('geburtsdatumMessage', 'Bitte ein Datum eingeben.', 'error'); return; }
|
||||
const today = new Date();
|
||||
const birth = new Date(val);
|
||||
const age = today.getFullYear() - birth.getFullYear()
|
||||
- (today < new Date(today.getFullYear(), birth.getMonth(), birth.getDate()) ? 1 : 0);
|
||||
if (age < 18) {
|
||||
showModalMessage('geburtsdatumMessage', 'Du musst mindestens 18 Jahre alt sein.', 'error');
|
||||
return;
|
||||
}
|
||||
const btn = document.getElementById('geburtsdatumConfirmBtn');
|
||||
btn.disabled = true; btn.textContent = 'Wird gespeichert…';
|
||||
hideModalMessage('geburtsdatumMessage');
|
||||
try {
|
||||
const res = await fetch('/user/me/geburtsdatum', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ geburtsdatum: val })
|
||||
});
|
||||
if (res.ok) {
|
||||
document.getElementById('gd-geburtsdatum').textContent = formatGeburtsdatum(val);
|
||||
closeGeburtsdatumDialog();
|
||||
showToast();
|
||||
} else if (res.status === 422) {
|
||||
showModalMessage('geburtsdatumMessage', 'Du musst mindestens 18 Jahre alt sein.', 'error');
|
||||
} else {
|
||||
showModalMessage('geburtsdatumMessage', `Fehler: HTTP ${res.status}`, 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
showModalMessage('geburtsdatumMessage', 'Server nicht erreichbar.', 'error');
|
||||
console.error(err);
|
||||
} finally {
|
||||
btn.disabled = false; btn.textContent = 'Speichern';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Nickname Dialog ──
|
||||
function openNameDialog() {
|
||||
document.getElementById('newName').value = '';
|
||||
hideModalMessage('nameMessage');
|
||||
document.getElementById('nameModal').classList.add('visible');
|
||||
document.getElementById('newName').focus();
|
||||
}
|
||||
|
||||
function closeNameDialog() {
|
||||
document.getElementById('nameModal').classList.remove('visible');
|
||||
}
|
||||
|
||||
async function saveName() {
|
||||
const newName = document.getElementById('newName').value.trim();
|
||||
if (!newName) { showModalMessage('nameMessage', 'Bitte einen Namen eingeben.', 'error'); return; }
|
||||
const btn = document.getElementById('nameConfirmBtn');
|
||||
btn.disabled = true; btn.textContent = 'Wird gespeichert…';
|
||||
hideModalMessage('nameMessage');
|
||||
try {
|
||||
const res = await fetch('/user/me/name', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: newName })
|
||||
});
|
||||
if (res.ok) {
|
||||
document.getElementById('gd-name').textContent = newName;
|
||||
closeNameDialog();
|
||||
showToast();
|
||||
} else if (res.status === 409) {
|
||||
showModalMessage('nameMessage', 'Dieser Nickname ist bereits vergeben.', 'error');
|
||||
} else {
|
||||
showModalMessage('nameMessage', `Fehler: HTTP ${res.status}`, 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
showModalMessage('nameMessage', 'Server nicht erreichbar.', 'error');
|
||||
console.error(err);
|
||||
} finally {
|
||||
btn.disabled = false; btn.textContent = 'Speichern';
|
||||
}
|
||||
}
|
||||
|
||||
// ── E-Mail Dialog ──
|
||||
function openEmailDialog() {
|
||||
document.getElementById('newEmail').value = '';
|
||||
hideModalMessage('emailMessage');
|
||||
document.getElementById('emailModal').classList.add('visible');
|
||||
document.getElementById('newEmail').focus();
|
||||
}
|
||||
|
||||
function closeEmailDialog() {
|
||||
document.getElementById('emailModal').classList.remove('visible');
|
||||
}
|
||||
|
||||
async function requestEmailChange() {
|
||||
const newEmail = document.getElementById('newEmail').value.trim();
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(newEmail)) {
|
||||
showModalMessage('emailMessage', 'Bitte eine gültige E-Mail-Adresse eingeben.', 'error');
|
||||
return;
|
||||
}
|
||||
const btn = document.getElementById('emailConfirmBtn');
|
||||
btn.disabled = true; btn.textContent = 'Wird gesendet…';
|
||||
hideModalMessage('emailMessage');
|
||||
try {
|
||||
const res = await fetch('/email-change', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ newEmail })
|
||||
});
|
||||
if (res.status === 202) {
|
||||
showModalMessage('emailMessage',
|
||||
'Bestätigungsmail wurde an die neue Adresse gesendet. Bitte bestätige die Änderung über den Link in der E-Mail.',
|
||||
'success');
|
||||
btn.disabled = true; btn.textContent = 'Gesendet';
|
||||
} else if (res.status === 409) {
|
||||
showModalMessage('emailMessage', 'Diese E-Mail-Adresse ist bereits vergeben.', 'error');
|
||||
btn.disabled = false; btn.textContent = 'Bestätigungsmail senden';
|
||||
} else {
|
||||
showModalMessage('emailMessage', `Fehler: HTTP ${res.status}`, 'error');
|
||||
btn.disabled = false; btn.textContent = 'Bestätigungsmail senden';
|
||||
}
|
||||
} catch (err) {
|
||||
showModalMessage('emailMessage', 'Server nicht erreichbar.', 'error');
|
||||
btn.disabled = false; btn.textContent = 'Bestätigungsmail senden';
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Konto löschen Dialog ──
|
||||
function openDeleteDialog() {
|
||||
hideModalMessage('deleteMessage');
|
||||
document.getElementById('deleteConfirmBtn').disabled = false;
|
||||
document.getElementById('deleteConfirmBtn').textContent = 'Konto löschen';
|
||||
document.getElementById('deleteModal').classList.add('visible');
|
||||
}
|
||||
|
||||
function closeDeleteDialog() {
|
||||
document.getElementById('deleteModal').classList.remove('visible');
|
||||
}
|
||||
|
||||
async function deleteAccount() {
|
||||
const btn = document.getElementById('deleteConfirmBtn');
|
||||
btn.disabled = true; btn.textContent = 'Wird gelöscht…';
|
||||
hideModalMessage('deleteMessage');
|
||||
try {
|
||||
const res = await fetch('/user/me', { method: 'DELETE' });
|
||||
if (res.ok) {
|
||||
window.location.href = '/login.html?accountDeleted=1';
|
||||
} else {
|
||||
showModalMessage('deleteMessage', `Fehler: HTTP ${res.status}`, 'error');
|
||||
btn.disabled = false; btn.textContent = 'Konto löschen';
|
||||
}
|
||||
} catch (err) {
|
||||
showModalMessage('deleteMessage', 'Server nicht erreichbar.', 'error');
|
||||
btn.disabled = false; btn.textContent = 'Konto löschen';
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
function showModalMessage(id, text, type) {
|
||||
const el = document.getElementById(id);
|
||||
el.textContent = text;
|
||||
el.className = `message ${type}`;
|
||||
el.style.display = 'block';
|
||||
}
|
||||
|
||||
function hideModalMessage(id) {
|
||||
document.getElementById(id).style.display = 'none';
|
||||
}
|
||||
|
||||
// Modal-Backdrop-Klick schließt Modals
|
||||
['nameModal','geburtsdatumModal','emailModal','deleteModal'].forEach(id => {
|
||||
document.getElementById(id).addEventListener('click', e => {
|
||||
if (e.target === document.getElementById(id)) document.getElementById(id).classList.remove('visible');
|
||||
});
|
||||
});
|
||||
document.getElementById('newName').addEventListener('keydown', e => { if (e.key === 'Enter') saveName(); });
|
||||
document.getElementById('newEmail').addEventListener('keydown', e => { if (e.key === 'Enter') requestEmailChange(); });
|
||||
|
||||
// ── Datenschutz laden ──
|
||||
async function loadPrivacy() {
|
||||
const meRes = await fetch('/login/me');
|
||||
if (!meRes.ok) return;
|
||||
const me = await meRes.json();
|
||||
myUserId = me.userId;
|
||||
|
||||
const profRes = await fetch('/social/users/' + myUserId);
|
||||
if (!profRes.ok) return;
|
||||
const profile = await profRes.json();
|
||||
|
||||
setValue('sv-grunddaten', profile.sichtbarkeitGrunddaten || 'ALLE');
|
||||
setValue('sv-galerie', profile.sichtbarkeitGalerie || 'ALLE');
|
||||
setValue('sv-freunde', profile.sichtbarkeitFreunde || 'ALLE');
|
||||
setValue('sv-feed', profile.sichtbarkeitFeed || 'ALLE');
|
||||
setValue('sv-pinnwand', profile.sichtbarkeitPinnwand || 'ALLE');
|
||||
setValue('sv-xp', profile.sichtbarkeitXp || 'ALLE');
|
||||
setValue('sv-lockhistorie', profile.sichtbarkeitLockhistorie || 'ALLE');
|
||||
}
|
||||
|
||||
function setValue(id, value) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.value = value;
|
||||
}
|
||||
|
||||
async function doSave() {
|
||||
const body = {
|
||||
sichtbarkeitGrunddaten: document.getElementById('sv-grunddaten').value,
|
||||
sichtbarkeitGalerie: document.getElementById('sv-galerie').value,
|
||||
sichtbarkeitFreunde: document.getElementById('sv-freunde').value,
|
||||
sichtbarkeitFeed: document.getElementById('sv-feed').value,
|
||||
sichtbarkeitPinnwand: document.getElementById('sv-pinnwand').value,
|
||||
sichtbarkeitXp: document.getElementById('sv-xp').value,
|
||||
sichtbarkeitLockhistorie: document.getElementById('sv-lockhistorie').value,
|
||||
};
|
||||
const res = await fetch('/user/me/privacy', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
if (res.ok) showToast();
|
||||
}
|
||||
|
||||
function showToast() {
|
||||
const t = document.getElementById('saveToast');
|
||||
t.classList.add('visible');
|
||||
clearTimeout(toastTimer);
|
||||
toastTimer = setTimeout(() => t.classList.remove('visible'), 2200);
|
||||
}
|
||||
|
||||
function openPreview(mode) {
|
||||
if (!myUserId) return;
|
||||
window.open('/benutzer.html?userId=' + myUserId + '&preview=' + mode, '_blank');
|
||||
}
|
||||
|
||||
// ── Benachrichtigungen laden ──
|
||||
async function loadNotifications() {
|
||||
const res = await fetch('/user/me/notifications');
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
for (const cause of ['INVITATION', 'GAME_STATE', 'EMERGENCY']) {
|
||||
const pref = data[cause] || { inApp: true, email: false };
|
||||
const inApp = document.getElementById('notif-' + cause + '-inApp');
|
||||
const email = document.getElementById('notif-' + cause + '-email');
|
||||
if (inApp) inApp.checked = pref.inApp;
|
||||
if (email) email.checked = pref.email;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveNotifications() {
|
||||
const body = {};
|
||||
for (const cause of ['INVITATION', 'GAME_STATE', 'EMERGENCY']) {
|
||||
const inApp = document.getElementById('notif-' + cause + '-inApp');
|
||||
const email = document.getElementById('notif-' + cause + '-email');
|
||||
body[cause] = {
|
||||
inApp: inApp ? inApp.checked : true,
|
||||
email: email ? email.checked : false
|
||||
};
|
||||
}
|
||||
const res = await fetch('/user/me/notifications', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
if (res.ok) showToast();
|
||||
}
|
||||
|
||||
// Hash-Navigation: Sektion anhand URL-Fragment öffnen
|
||||
function openSectionFromHash() {
|
||||
const hash = window.location.hash.replace('#', '');
|
||||
if (hash) {
|
||||
const el = document.getElementById(hash);
|
||||
if (el) el.classList.add('open');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Spiel Einstellungen – BDSM ──
|
||||
function applyCheckItems(groupId, values) {
|
||||
document.querySelectorAll(`#${groupId} .spiel-check-item input`).forEach(cb => {
|
||||
const checked = values.includes(cb.value);
|
||||
cb.checked = checked;
|
||||
cb.closest('.spiel-check-item').classList.toggle('is-checked', checked);
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('.spiel-check-item input').forEach(cb => {
|
||||
cb.addEventListener('change', () => {
|
||||
cb.closest('.spiel-check-item').classList.toggle('is-checked', cb.checked);
|
||||
saveBdsmDefaults();
|
||||
});
|
||||
});
|
||||
|
||||
async function loadBdsmDefaults() {
|
||||
const res = await fetch('/user/me/bdsm-defaults');
|
||||
if (!res.ok) return;
|
||||
const d = await res.json();
|
||||
applyCheckItems('bdsm-spieltmit', d.spieltMit || []);
|
||||
applyCheckItems('bdsm-rollen', d.rollen || []);
|
||||
applyCheckItems('bdsm-werkzeuge', d.werkzeuge || []);
|
||||
}
|
||||
|
||||
async function saveBdsmDefaults() {
|
||||
const get = groupId => [...document.querySelectorAll(`#${groupId} input:checked`)].map(cb => cb.value);
|
||||
await fetch('/user/me/bdsm-defaults', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
spieltMit: get('bdsm-spieltmit'),
|
||||
rollen: get('bdsm-rollen'),
|
||||
werkzeuge: get('bdsm-werkzeuge'),
|
||||
})
|
||||
});
|
||||
showToast();
|
||||
}
|
||||
|
||||
loadGrunddaten();
|
||||
loadPrivacy();
|
||||
loadNotifications();
|
||||
loadBdsmDefaults();
|
||||
openSectionFromHash();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -13,8 +13,8 @@
|
||||
label: 'BDSM Game',
|
||||
icon: '◆',
|
||||
items: [
|
||||
{ href: '/sessionbdsm.html', icon: '▷', label: 'Neue Session', id: 'navBdsmNeu' },
|
||||
{ href: '/sessionbdsmingame.html', icon: '▶', label: 'Im Spiel', id: 'navBdsmImSpiel' },
|
||||
{ href: '/bdsm.html', icon: '▷', label: 'Neue Session', id: 'navBdsmNeu' },
|
||||
{ href: '/bdsmingame.html', icon: '▶', label: 'Im Spiel', id: 'navBdsmImSpiel' },
|
||||
{ href: '/aufgaben.html', icon: '✓', label: 'Aufgaben' },
|
||||
{ href: '/toys.html', icon: '◈', label: 'Toys' },
|
||||
{ href: '/entdecken.html', icon: '⊙', label: 'Entdecken' },
|
||||
@@ -105,7 +105,7 @@
|
||||
|
||||
// BDSM Session-Status
|
||||
try {
|
||||
const sessionRes = await fetch(`/session?userId=${user.userId}`);
|
||||
const sessionRes = await fetch(`/bdsm?userId=${user.userId}`);
|
||||
const hasSession = sessionRes.status === 200;
|
||||
if (navNeu) navNeu.style.display = hasSession ? 'none' : '';
|
||||
if (navImSpiel) navImSpiel.style.display = hasSession ? '' : 'none';
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
<li><hr style="border:none;border-top:1px solid var(--color-secondary);margin:0.4rem 1rem;"></li>
|
||||
${desktopItems}
|
||||
<li style="margin-top:auto;"><hr style="border:none;border-top:1px solid var(--color-secondary);margin:0.4rem 1rem;"></li>
|
||||
<li><a href="/einstellungen.html"${path === '/einstellungen.html' ? ' class="active"' : ''}><span class="icon">⚙️</span> Einstellungen</a></li>
|
||||
<li><a href="/login/logout"><span class="icon">⏏</span> Abmelden</a></li>
|
||||
</ul>`;
|
||||
|
||||
@@ -66,11 +67,12 @@
|
||||
</li>`;
|
||||
|
||||
const sep = '<li class="sidebar-mobile-only"><hr style="border:none;border-top:1px solid var(--color-secondary);margin:0.4rem 1rem;"></li>';
|
||||
const mobileSettings = `<li class="sidebar-mobile-only"><a href="/einstellungen.html"${path === '/einstellungen.html' ? ' class="active"' : ''}><span class="icon">⚙️</span> Einstellungen</a></li>`;
|
||||
const logoutLi = sidebarUl.querySelector('a[href="/login/logout"]')?.closest('li');
|
||||
if (logoutLi) {
|
||||
logoutLi.insertAdjacentHTML('beforebegin', sep + mobileLinks + mobileProfile);
|
||||
logoutLi.insertAdjacentHTML('beforebegin', sep + mobileLinks + mobileProfile + mobileSettings);
|
||||
} else {
|
||||
sidebarUl.insertAdjacentHTML('beforeend', sep + mobileLinks + mobileProfile);
|
||||
sidebarUl.insertAdjacentHTML('beforeend', sep + mobileLinks + mobileProfile + mobileSettings);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Profil – XXX The Game</title>
|
||||
@@ -269,35 +269,19 @@
|
||||
<body class="app">
|
||||
|
||||
<div class="main">
|
||||
<div class="content" style="max-width: 600px;">
|
||||
<div class="content">
|
||||
|
||||
<div class="profile-picture-wrap">
|
||||
<div class="profile-picture" id="profilePicDisplay">◉</div>
|
||||
<input type="file" id="picFile" accept="image/*">
|
||||
</div>
|
||||
|
||||
<div class="profile-field">
|
||||
<label>Nickname</label>
|
||||
<div class="profile-field-row">
|
||||
<p id="userName"></p>
|
||||
<button onclick="openNameDialog()">Nickname ändern</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="profile-field">
|
||||
<label>E-Mail</label>
|
||||
<div class="profile-field-row">
|
||||
<p id="userEmail"></p>
|
||||
<button onclick="openEmailDialog()">E-Mail ändern</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Freiwillige Profilangaben -->
|
||||
<div class="gallery-section-label" style="margin-top:1.25rem;">Freiwillige Angaben</div>
|
||||
<div class="profile-extras-grid">
|
||||
<div>
|
||||
<label>Alter</label>
|
||||
<input type="number" id="profileAlter" min="18" max="99" placeholder="—">
|
||||
<input type="text" id="profileAlter" readonly style="background:transparent;cursor:default;color:var(--color-muted);" placeholder="—">
|
||||
</div>
|
||||
<div>
|
||||
<label>Größe (cm)</label>
|
||||
@@ -346,10 +330,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-delete-row">
|
||||
<button class="btn-delete" onclick="openDeleteDialog()">Konto löschen</button>
|
||||
</div>
|
||||
|
||||
<div class="message" id="message"></div>
|
||||
|
||||
<button class="full-width" id="saveBtn" onclick="saveProfile()">Profil speichern</button>
|
||||
@@ -364,50 +344,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nickname modal -->
|
||||
<div class="modal-backdrop" id="nameModal">
|
||||
<div class="modal">
|
||||
<h2>Nickname ändern</h2>
|
||||
<label for="newName">Neuer Nickname</label>
|
||||
<input type="text" id="newName" placeholder="Neuer Name" autocomplete="off">
|
||||
<div class="message" id="nameMessage"></div>
|
||||
<div class="modal-actions">
|
||||
<button class="secondary" onclick="closeNameDialog()">Abbrechen</button>
|
||||
<button id="nameConfirmBtn" onclick="saveName()">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Konto löschen modal -->
|
||||
<div class="modal-backdrop" id="deleteModal">
|
||||
<div class="modal">
|
||||
<h2>Konto löschen</h2>
|
||||
<p style="color:var(--color-muted); font-size:0.9rem; margin-bottom:0.75rem;">
|
||||
Dein Konto sowie alle gespeicherten Aufgaben und Toys werden unwiderruflich gelöscht.
|
||||
</p>
|
||||
<p style="color:var(--color-primary); font-size:0.85rem;">Dieser Vorgang kann nicht rückgängig gemacht werden.</p>
|
||||
<div class="message" id="deleteMessage"></div>
|
||||
<div class="modal-actions">
|
||||
<button class="secondary" onclick="closeDeleteDialog()">Abbrechen</button>
|
||||
<button id="deleteConfirmBtn" onclick="deleteAccount()" style="background:#c0392b;">Konto löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- E-Mail modal -->
|
||||
<div class="modal-backdrop" id="emailModal">
|
||||
<div class="modal">
|
||||
<h2>E-Mail-Adresse ändern</h2>
|
||||
<label for="newEmail">Neue E-Mail-Adresse</label>
|
||||
<input type="email" id="newEmail" placeholder="neue@email.de" autocomplete="off">
|
||||
<div class="message" id="emailMessage"></div>
|
||||
<div class="modal-actions">
|
||||
<button class="secondary" onclick="closeEmailDialog()">Abbrechen</button>
|
||||
<button id="emailConfirmBtn" onclick="requestEmailChange()">Bestätigungsmail senden</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/shared.js"></script>
|
||||
<script src="/js/image-viewer.js"></script>
|
||||
<script src="/js/sidebar.js"></script>
|
||||
@@ -424,14 +360,18 @@
|
||||
})
|
||||
.then(user => {
|
||||
if (!user) return;
|
||||
document.getElementById('userName').textContent = user.name;
|
||||
document.getElementById('userEmail').textContent = user.email;
|
||||
if (user.profilePicture) {
|
||||
currentPicture = user.profilePicture;
|
||||
renderPicture(currentPicture);
|
||||
}
|
||||
// Fill optional profile fields
|
||||
if (user.alter) document.getElementById('profileAlter').value = user.alter;
|
||||
if (user.geburtsdatum) {
|
||||
const birth = new Date(user.geburtsdatum);
|
||||
const today = new Date();
|
||||
const age = today.getFullYear() - birth.getFullYear()
|
||||
- (today < new Date(today.getFullYear(), birth.getMonth(), birth.getDate()) ? 1 : 0);
|
||||
document.getElementById('profileAlter').value = age + ' Jahre';
|
||||
}
|
||||
if (user.groesse) document.getElementById('profileGroesse').value = user.groesse;
|
||||
if (user.gewicht) document.getElementById('profileGewicht').value = user.gewicht;
|
||||
if (user.geschlecht) document.getElementById('profileGeschlecht').value = user.geschlecht;
|
||||
@@ -506,7 +446,6 @@
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
alter: toNullOrInt('profileAlter'),
|
||||
groesse: toNullOrInt('profileGroesse'),
|
||||
gewicht: toNullOrInt('profileGewicht'),
|
||||
geschlecht: toNullOrStr('profileGeschlecht'),
|
||||
@@ -530,105 +469,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ── Nickname dialog ──
|
||||
function openNameDialog() {
|
||||
document.getElementById('newName').value = '';
|
||||
hideModalMessage('nameMessage');
|
||||
document.getElementById('nameModal').classList.add('visible');
|
||||
document.getElementById('newName').focus();
|
||||
}
|
||||
|
||||
function closeNameDialog() {
|
||||
document.getElementById('nameModal').classList.remove('visible');
|
||||
}
|
||||
|
||||
async function saveName() {
|
||||
const newName = document.getElementById('newName').value.trim();
|
||||
if (!newName) {
|
||||
showModalMessage('nameMessage', 'Bitte einen Namen eingeben.', 'error');
|
||||
return;
|
||||
}
|
||||
const btn = document.getElementById('nameConfirmBtn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Wird gespeichert…';
|
||||
hideModalMessage('nameMessage');
|
||||
|
||||
try {
|
||||
const response = await fetch('/user/me/name', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: newName })
|
||||
});
|
||||
if (response.ok) {
|
||||
document.getElementById('userName').textContent = newName;
|
||||
closeNameDialog();
|
||||
showMessage('Nickname geändert.', 'success');
|
||||
} else if (response.status === 409) {
|
||||
showModalMessage('nameMessage', 'Dieser Nickname ist bereits vergeben.', 'error');
|
||||
} else {
|
||||
showModalMessage('nameMessage', `Fehler: HTTP ${response.status}`, 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
showModalMessage('nameMessage', 'Server nicht erreichbar.', 'error');
|
||||
console.error(err);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Speichern';
|
||||
}
|
||||
}
|
||||
|
||||
// ── E-Mail dialog ──
|
||||
function openEmailDialog() {
|
||||
document.getElementById('newEmail').value = '';
|
||||
hideModalMessage('emailMessage');
|
||||
document.getElementById('emailModal').classList.add('visible');
|
||||
document.getElementById('newEmail').focus();
|
||||
}
|
||||
|
||||
function closeEmailDialog() {
|
||||
document.getElementById('emailModal').classList.remove('visible');
|
||||
}
|
||||
|
||||
async function requestEmailChange() {
|
||||
const newEmail = document.getElementById('newEmail').value.trim();
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(newEmail)) {
|
||||
showModalMessage('emailMessage', 'Bitte eine gültige E-Mail-Adresse eingeben.', 'error');
|
||||
return;
|
||||
}
|
||||
const btn = document.getElementById('emailConfirmBtn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Wird gesendet…';
|
||||
hideModalMessage('emailMessage');
|
||||
|
||||
try {
|
||||
const response = await fetch('/email-change', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ newEmail })
|
||||
});
|
||||
if (response.status === 202) {
|
||||
showModalMessage('emailMessage',
|
||||
'Bestätigungsmail wurde an die neue Adresse gesendet. Bitte bestätige die Änderung über den Link in der E-Mail.',
|
||||
'success');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Gesendet';
|
||||
} else if (response.status === 409) {
|
||||
showModalMessage('emailMessage', 'Diese E-Mail-Adresse ist bereits vergeben.', 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Bestätigungsmail senden';
|
||||
} else {
|
||||
showModalMessage('emailMessage', `Fehler: HTTP ${response.status}`, 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Bestätigungsmail senden';
|
||||
}
|
||||
} catch (err) {
|
||||
showModalMessage('emailMessage', 'Server nicht erreichbar.', 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Bestätigungsmail senden';
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Gallery ──
|
||||
let myUserId = null;
|
||||
|
||||
@@ -755,71 +595,6 @@
|
||||
function hideMessage() {
|
||||
document.getElementById('message').style.display = 'none';
|
||||
}
|
||||
|
||||
function showModalMessage(id, text, type) {
|
||||
const el = document.getElementById(id);
|
||||
el.textContent = text;
|
||||
el.className = `message ${type}`;
|
||||
el.style.display = 'block';
|
||||
}
|
||||
|
||||
function hideModalMessage(id) {
|
||||
document.getElementById(id).style.display = 'none';
|
||||
}
|
||||
|
||||
// ── Konto löschen ──
|
||||
function openDeleteDialog() {
|
||||
hideModalMessage('deleteMessage');
|
||||
document.getElementById('deleteConfirmBtn').disabled = false;
|
||||
document.getElementById('deleteConfirmBtn').textContent = 'Konto löschen';
|
||||
document.getElementById('deleteModal').classList.add('visible');
|
||||
}
|
||||
|
||||
function closeDeleteDialog() {
|
||||
document.getElementById('deleteModal').classList.remove('visible');
|
||||
}
|
||||
|
||||
async function deleteAccount() {
|
||||
const btn = document.getElementById('deleteConfirmBtn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Wird gelöscht…';
|
||||
hideModalMessage('deleteMessage');
|
||||
|
||||
try {
|
||||
const response = await fetch('/user/me', { method: 'DELETE' });
|
||||
if (response.ok) {
|
||||
window.location.href = '/login.html?accountDeleted=1';
|
||||
} else {
|
||||
showModalMessage('deleteMessage', `Fehler: HTTP ${response.status}`, 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Konto löschen';
|
||||
}
|
||||
} catch (err) {
|
||||
showModalMessage('deleteMessage', 'Server nicht erreichbar.', 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Konto löschen';
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
// Close modals on backdrop click
|
||||
document.getElementById('deleteModal').addEventListener('click', e => {
|
||||
if (e.target === document.getElementById('deleteModal')) closeDeleteDialog();
|
||||
});
|
||||
document.getElementById('nameModal').addEventListener('click', e => {
|
||||
if (e.target === document.getElementById('nameModal')) closeNameDialog();
|
||||
});
|
||||
document.getElementById('emailModal').addEventListener('click', e => {
|
||||
if (e.target === document.getElementById('emailModal')) closeEmailDialog();
|
||||
});
|
||||
|
||||
// Enter key in modal inputs
|
||||
document.getElementById('newName').addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') saveName();
|
||||
});
|
||||
document.getElementById('newEmail').addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') requestEmailChange();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>xXx Games – Neues Konto erstellen</title>
|
||||
@@ -19,6 +19,9 @@
|
||||
<label for="email">E-Mail</label>
|
||||
<input type="email" id="email" placeholder="deine@email.de" autocomplete="email" />
|
||||
|
||||
<label for="geburtsdatum">Geburtsdatum</label>
|
||||
<input type="date" id="geburtsdatum" autocomplete="bday" />
|
||||
|
||||
<label for="password">Passwort</label>
|
||||
<input type="password" id="password" placeholder="••••••••" autocomplete="new-password" />
|
||||
|
||||
@@ -50,11 +53,12 @@
|
||||
async function register() {
|
||||
const name = document.getElementById('name').value.trim();
|
||||
const email = document.getElementById('email').value.trim();
|
||||
const geburtsdatum = document.getElementById('geburtsdatum').value;
|
||||
const password = document.getElementById('password').value;
|
||||
const passwordConfirm = document.getElementById('passwordConfirm').value;
|
||||
const btn = document.getElementById('registerBtn');
|
||||
|
||||
if (!name || !email || !password || !passwordConfirm) {
|
||||
if (!name || !email || !geburtsdatum || !password || !passwordConfirm) {
|
||||
showMessage('Bitte alle Felder ausfüllen.', 'error');
|
||||
return;
|
||||
}
|
||||
@@ -62,6 +66,14 @@
|
||||
showMessage('Bitte eine gültige E-Mail-Adresse eingeben.', 'error');
|
||||
return;
|
||||
}
|
||||
const today = new Date();
|
||||
const birth = new Date(geburtsdatum);
|
||||
const age = today.getFullYear() - birth.getFullYear()
|
||||
- (today < new Date(today.getFullYear(), birth.getMonth(), birth.getDate()) ? 1 : 0);
|
||||
if (age < 18) {
|
||||
showMessage('Du musst mindestens 18 Jahre alt sein, um dich zu registrieren.', 'error');
|
||||
return;
|
||||
}
|
||||
if (password !== passwordConfirm) {
|
||||
showMessage('Die Passwörter stimmen nicht überein.', 'error');
|
||||
return;
|
||||
@@ -76,11 +88,15 @@
|
||||
const response = await fetch('/registration', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, email, passwordHash })
|
||||
body: JSON.stringify({ name, email, passwordHash, geburtsdatum })
|
||||
});
|
||||
|
||||
if (response.status === 202) {
|
||||
window.location.href = `/activate.html?email=${encodeURIComponent(email)}`;
|
||||
} else if (response.status === 422) {
|
||||
showMessage('Du musst mindestens 18 Jahre alt sein, um dich zu registrieren.', 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Registrieren';
|
||||
} else if (response.status === 400) {
|
||||
showMessage('Diese E-Mail-Adresse ist bereits registriert.', 'error');
|
||||
btn.disabled = false;
|
||||
|
||||
@@ -1,439 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BDSM Game – Mitspieler – XXX The Game</title>
|
||||
<link rel="stylesheet" href="/css/variables.css">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<style>
|
||||
.session-setup { max-width: 700px; }
|
||||
|
||||
.setup-section { margin-bottom: 2.5rem; }
|
||||
.setup-section h2 {
|
||||
color: var(--color-primary);
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1.25rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--color-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* ── Player cards ── */
|
||||
.player-card {
|
||||
background: var(--color-card);
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-radius: 10px;
|
||||
padding: 1.25rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.player-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.player-title {
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
font-size: 1rem;
|
||||
}
|
||||
.player-badge {
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
font-size: 0.7rem;
|
||||
padding: 0.1rem 0.5rem;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.player-remove {
|
||||
margin-left: auto;
|
||||
background: transparent;
|
||||
border: 1px solid var(--color-secondary);
|
||||
color: var(--color-muted);
|
||||
padding: 0.25rem 0.6rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: normal;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s, color 0.15s;
|
||||
}
|
||||
.player-remove:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.card-field { margin-bottom: 1rem; }
|
||||
.card-field > label {
|
||||
font-size: 0.8rem;
|
||||
color: #aaa;
|
||||
margin: 0 0 0.5rem 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ── Check/radio items ── */
|
||||
.check-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.check-group--two-col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
.check-item {
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.45rem;
|
||||
background: var(--color-secondary);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
padding: 0.4rem 0.7rem;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s;
|
||||
user-select: none;
|
||||
}
|
||||
.check-item.is-checked { border-color: var(--color-primary); }
|
||||
.check-item input {
|
||||
accent-color: var(--color-primary);
|
||||
width: auto;
|
||||
margin-top: 0.15rem;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.check-item-label {
|
||||
font-size: 0.88rem;
|
||||
color: var(--color-text);
|
||||
line-height: 1.3;
|
||||
}
|
||||
.check-item-desc {
|
||||
display: block;
|
||||
font-size: 0.72rem;
|
||||
color: var(--color-muted);
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
|
||||
.add-player-btn {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border: 1px dashed var(--color-secondary);
|
||||
color: var(--color-muted);
|
||||
padding: 0.75rem;
|
||||
border-radius: 10px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s, color 0.15s;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.add-player-btn:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-text);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.field-error {
|
||||
font-size: 0.78rem;
|
||||
color: var(--color-primary);
|
||||
margin-top: 0.3rem;
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="app">
|
||||
<div class="main">
|
||||
<div class="content session-setup">
|
||||
|
||||
<h1>BDSM Game</h1>
|
||||
<p style="margin-bottom:2rem;">Schritt 2 von 4 – Mitspieler</p>
|
||||
|
||||
<div class="setup-section">
|
||||
<h2>Mitspieler</h2>
|
||||
<div id="playersContainer"></div>
|
||||
<button class="add-player-btn" onclick="addPlayer()">+ Spieler hinzufügen</button>
|
||||
</div>
|
||||
|
||||
<div class="message" id="message"></div>
|
||||
<div style="display:flex; gap:1rem;">
|
||||
<button style="flex:1;" class="secondary" onclick="window.location.href='/sessionbdsm.html'">← Zurück</button>
|
||||
<button style="flex:2;" onclick="weiter()">Weiter</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/sidebar.js"></script>
|
||||
<script>
|
||||
// Redirect back if settings are missing
|
||||
if (!sessionStorage.getItem('bdsm-session-settings')) {
|
||||
window.location.replace('/sessionbdsm.html');
|
||||
}
|
||||
|
||||
const GESCHLECHTER = [
|
||||
{ value: 'MAENNLICH', label: 'Männlich' },
|
||||
{ value: 'WEIBLICH', label: 'Weiblich' },
|
||||
{ value: 'DIVERS', label: 'Divers' },
|
||||
];
|
||||
|
||||
const ROLLEN = [
|
||||
{ value: 'AUFGABE_AKTIV', label: 'Aufgabe – Aktiv' },
|
||||
{ value: 'AUFGABE_PASSIV', label: 'Aufgabe – Passiv' },
|
||||
{ value: 'BESTRAFUNG_AKTIV', label: 'Bestrafung – Aktiv' },
|
||||
{ value: 'BESTRAFUNG_PASSIV', label: 'Bestrafung – Passiv' },
|
||||
];
|
||||
|
||||
const WERKZEUGE_DEFAULTS = {
|
||||
MAENNLICH: ['MUND', 'PENIS', 'ANUS', 'UMSCHNALLDILDO'],
|
||||
WEIBLICH: ['MUND', 'VAGINA', 'ANUS', 'UMSCHNALLDILDO'],
|
||||
DIVERS: ['MUND', 'ANUS', 'UMSCHNALLDILDO'],
|
||||
};
|
||||
|
||||
const WERKZEUGE = [
|
||||
{ value: 'MUND', label: 'Mund', desc: 'Gewillt den Mund einzusetzen' },
|
||||
{ value: 'VAGINA', label: 'Vagina', desc: 'Verfügt über eine Vagina und setzt sie ein' },
|
||||
{ value: 'PENIS', label: 'Penis', desc: 'Verfügt über einen Penis und setzt ihn ein' },
|
||||
{ value: 'ANUS', label: 'Anus', desc: 'Gewillt den Anus einzusetzen' },
|
||||
{ value: 'UMSCHNALLDILDO', label: 'Umschnall-Dildo', desc: 'Verfügt über einen Umschnall-Dildo' },
|
||||
];
|
||||
|
||||
let playerSeq = 0;
|
||||
let playerIds = [];
|
||||
|
||||
function buildCheckItems(name, items, type) {
|
||||
return items.map(({ value, label, desc }) => `
|
||||
<label class="check-item">
|
||||
<input type="${type}" name="${name}" value="${value}">
|
||||
<span>
|
||||
<span class="check-item-label">${label}</span>
|
||||
${desc ? `<span class="check-item-desc">${desc}</span>` : ''}
|
||||
</span>
|
||||
</label>`).join('');
|
||||
}
|
||||
|
||||
function createCardHtml(id, prefillName, isSelf) {
|
||||
const badge = isSelf ? '<span class="player-badge">Du</span>' : '';
|
||||
const num = playerIds.indexOf(id) + 1;
|
||||
return `
|
||||
<div class="player-card" id="player-${id}">
|
||||
<div class="player-card-header">
|
||||
<span class="player-title">Spieler ${num}</span>
|
||||
${badge}
|
||||
<button class="player-remove" onclick="removePlayer(${id})">✕ Entfernen</button>
|
||||
</div>
|
||||
<div class="card-field">
|
||||
<label>Name</label>
|
||||
<input type="text" id="p${id}-name" value="${prefillName}" placeholder="Name" autocomplete="off">
|
||||
<div class="field-error" id="p${id}-name-err">Bitte Namen eingeben.</div>
|
||||
</div>
|
||||
<div class="card-field">
|
||||
<label>Geschlecht</label>
|
||||
<div class="check-group">${buildCheckItems('p' + id + '-geschlecht', GESCHLECHTER, 'radio')}</div>
|
||||
<div class="field-error" id="p${id}-geschlecht-err">Bitte Geschlecht auswählen.</div>
|
||||
</div>
|
||||
<div class="card-field">
|
||||
<label>Spielt mit</label>
|
||||
<div class="check-group">${buildCheckItems('p' + id + '-spieltmit', GESCHLECHTER, 'checkbox')}</div>
|
||||
<div class="field-error" id="p${id}-spieltmit-err">Bitte mindestens eine Option wählen.</div>
|
||||
<div class="field-error" id="p${id}-partner-err">Kein Mitspieler mit passendem Geschlecht vorhanden.</div>
|
||||
</div>
|
||||
<div class="card-field">
|
||||
<label>Rollen</label>
|
||||
<div class="check-group">${buildCheckItems('p' + id + '-rollen', ROLLEN, 'checkbox')}</div>
|
||||
<div class="field-error" id="p${id}-rollen-err">Bitte mindestens eine Rolle wählen.</div>
|
||||
</div>
|
||||
<div class="card-field">
|
||||
<label>Verfügbar</label>
|
||||
<div class="check-group check-group--two-col">${buildCheckItems('p' + id + '-werkzeuge', WERKZEUGE, 'checkbox')}</div>
|
||||
<div class="field-error" id="p${id}-werkzeuge-err">Bitte mindestens ein Werkzeug wählen.</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function addPlayer(prefillName = '', isSelf = false) {
|
||||
playerSeq++;
|
||||
const id = playerSeq;
|
||||
playerIds.push(id);
|
||||
document.getElementById('playersContainer')
|
||||
.insertAdjacentHTML('beforeend', createCardHtml(id, prefillName, isSelf));
|
||||
refreshRemoveButtons();
|
||||
}
|
||||
|
||||
function removePlayer(id) {
|
||||
document.getElementById('player-' + id)?.remove();
|
||||
playerIds = playerIds.filter(x => x !== id);
|
||||
refreshPlayerTitles();
|
||||
refreshRemoveButtons();
|
||||
}
|
||||
|
||||
function refreshPlayerTitles() {
|
||||
playerIds.forEach((id, idx) => {
|
||||
const el = document.querySelector(`#player-${id} .player-title`);
|
||||
if (el) el.textContent = 'Spieler ' + (idx + 1);
|
||||
});
|
||||
}
|
||||
|
||||
function refreshRemoveButtons() {
|
||||
const canRemove = playerIds.length > 2;
|
||||
playerIds.forEach(id => {
|
||||
const btn = document.querySelector(`#player-${id} .player-remove`);
|
||||
if (btn) btn.style.display = canRemove ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('change', e => {
|
||||
const input = e.target;
|
||||
if (input.type !== 'checkbox' && input.type !== 'radio') return;
|
||||
if (input.type === 'radio') {
|
||||
document.querySelectorAll(`input[name="${input.name}"]`).forEach(r => {
|
||||
r.closest('.check-item')?.classList.toggle('is-checked', r.checked);
|
||||
});
|
||||
// Werkzeuge vorauswählen wenn Geschlecht gewählt wird
|
||||
if (input.checked && input.name.endsWith('-geschlecht')) {
|
||||
const prefix = input.name.slice(0, -'-geschlecht'.length);
|
||||
const defaults = WERKZEUGE_DEFAULTS[input.value] || [];
|
||||
document.querySelectorAll(`input[name="${prefix}-werkzeuge"]`).forEach(cb => {
|
||||
cb.checked = defaults.includes(cb.value);
|
||||
cb.closest('.check-item')?.classList.toggle('is-checked', cb.checked);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
input.closest('.check-item')?.classList.toggle('is-checked', input.checked);
|
||||
}
|
||||
});
|
||||
|
||||
function getChecked(name) {
|
||||
return [...document.querySelectorAll(`input[name="${name}"]:checked`)].map(el => el.value);
|
||||
}
|
||||
|
||||
function setFieldError(id, show) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.style.display = show ? 'block' : 'none';
|
||||
}
|
||||
|
||||
const ROLE_LABELS = {
|
||||
AUFGABE_AKTIV: 'Aufgabe – Aktiv',
|
||||
AUFGABE_PASSIV: 'Aufgabe – Passiv',
|
||||
BESTRAFUNG_AKTIV: 'Bestrafung – Aktiv',
|
||||
BESTRAFUNG_PASSIV: 'Bestrafung – Passiv',
|
||||
};
|
||||
|
||||
function weiter() {
|
||||
hideMessage();
|
||||
let valid = true;
|
||||
|
||||
// Reset cross-player errors
|
||||
playerIds.forEach(id => setFieldError(`p${id}-partner-err`, false));
|
||||
|
||||
const mitspieler = playerIds.map(id => {
|
||||
const name = document.getElementById(`p${id}-name`).value.trim();
|
||||
const geschlecht = getChecked(`p${id}-geschlecht`);
|
||||
const spieltMit = getChecked(`p${id}-spieltmit`);
|
||||
const rollen = getChecked(`p${id}-rollen`);
|
||||
const werkzeuge = getChecked(`p${id}-werkzeuge`);
|
||||
|
||||
setFieldError(`p${id}-name-err`, !name);
|
||||
setFieldError(`p${id}-geschlecht-err`, geschlecht.length === 0);
|
||||
setFieldError(`p${id}-spieltmit-err`, spieltMit.length === 0);
|
||||
setFieldError(`p${id}-rollen-err`, rollen.length === 0);
|
||||
setFieldError(`p${id}-werkzeuge-err`, werkzeuge.length === 0);
|
||||
|
||||
if (!name || geschlecht.length === 0 || spieltMit.length === 0 || rollen.length === 0 || werkzeuge.length === 0) {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return { name, geschlecht: geschlecht[0] || null, spieltMit, rollen, werkzeuge };
|
||||
});
|
||||
|
||||
if (!valid) {
|
||||
showMessage('Bitte alle Felder für jeden Spieler ausfüllen.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Jede Rolle muss mindestens einmal vergeben sein
|
||||
const allRoles = new Set(mitspieler.flatMap(p => p.rollen));
|
||||
const missingRoles = Object.keys(ROLE_LABELS).filter(r => !allRoles.has(r));
|
||||
if (missingRoles.length > 0) {
|
||||
showMessage('Folgende Rollen müssen mindestens einmal vergeben sein: ' +
|
||||
missingRoles.map(r => ROLE_LABELS[r]).join(', '), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Jeder Spieler braucht mindestens einen Mitspieler mit passendem Geschlecht
|
||||
let partnerFehler = false;
|
||||
mitspieler.forEach((player, i) => {
|
||||
const andereGeschlechter = mitspieler
|
||||
.filter((_, j) => j !== i)
|
||||
.map(p => p.geschlecht);
|
||||
const hatPartner = player.spieltMit.some(g => andereGeschlechter.includes(g));
|
||||
if (!hatPartner) {
|
||||
setFieldError(`p${playerIds[i]}-partner-err`, true);
|
||||
partnerFehler = true;
|
||||
}
|
||||
});
|
||||
if (partnerFehler) {
|
||||
showMessage('Mindestens ein Spieler hat keinen kompatiblen Mitspieler.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = JSON.parse(sessionStorage.getItem('bdsm-session-settings'));
|
||||
sessionStorage.setItem('bdsm-session-setup', JSON.stringify({ settings, mitspieler }));
|
||||
window.location.href = '/sessionbdsmtasks.html';
|
||||
}
|
||||
|
||||
function showMessage(text, type) {
|
||||
const el = document.getElementById('message');
|
||||
el.textContent = text;
|
||||
el.className = `message ${type}`;
|
||||
el.style.display = 'block';
|
||||
}
|
||||
|
||||
function hideMessage() {
|
||||
document.getElementById('message').style.display = 'none';
|
||||
}
|
||||
|
||||
function restorePlayer(id, data) {
|
||||
if (data.geschlecht) {
|
||||
const radio = document.querySelector(`input[name="p${id}-geschlecht"][value="${data.geschlecht}"]`);
|
||||
if (radio) { radio.checked = true; radio.closest('.check-item')?.classList.add('is-checked'); }
|
||||
}
|
||||
(data.spieltMit || []).forEach(val => {
|
||||
const cb = document.querySelector(`input[name="p${id}-spieltmit"][value="${val}"]`);
|
||||
if (cb) { cb.checked = true; cb.closest('.check-item')?.classList.add('is-checked'); }
|
||||
});
|
||||
(data.rollen || []).forEach(val => {
|
||||
const cb = document.querySelector(`input[name="p${id}-rollen"][value="${val}"]`);
|
||||
if (cb) { cb.checked = true; cb.closest('.check-item')?.classList.add('is-checked'); }
|
||||
});
|
||||
(data.werkzeuge || []).forEach(val => {
|
||||
const cb = document.querySelector(`input[name="p${id}-werkzeuge"][value="${val}"]`);
|
||||
if (cb) { cb.checked = true; cb.closest('.check-item')?.classList.add('is-checked'); }
|
||||
});
|
||||
}
|
||||
|
||||
// Init: gespeicherte Spieler wiederherstellen oder neu anlegen
|
||||
const savedSetup = sessionStorage.getItem('bdsm-session-setup');
|
||||
if (savedSetup) {
|
||||
const { mitspieler } = JSON.parse(savedSetup);
|
||||
mitspieler.forEach((p, i) => {
|
||||
addPlayer(p.name, i === 0);
|
||||
restorePlayer(playerIds[playerIds.length - 1], p);
|
||||
});
|
||||
} else {
|
||||
fetch('/login/me')
|
||||
.then(r => r.ok ? r.json() : null)
|
||||
.then(user => {
|
||||
addPlayer(user ? user.name : '', true);
|
||||
addPlayer();
|
||||
})
|
||||
.catch(() => {
|
||||
addPlayer('', true);
|
||||
addPlayer();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -54,7 +54,7 @@
|
||||
Tauche ein in strukturierte Sessions mit Aufgaben, Toys und klaren Rollen.
|
||||
Definiere Grenzen, vergib Aufgaben und erlebe intensive Momente mit deinem Partner.
|
||||
</p>
|
||||
<a href="/sessionbdsm.html"><button class="game-card-btn">Neue Session starten</button></a>
|
||||
<a href="/bdsm.html"><button class="game-card-btn">Neue Session starten</button></a>
|
||||
</div>
|
||||
|
||||
<div class="game-card">
|
||||
|
||||
Reference in New Issue
Block a user