Alles mögliche
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled

This commit is contained in:
2026-04-08 00:53:49 +02:00
parent 5ffb99c9b5
commit a13b8e894f
78 changed files with 2833 additions and 650 deletions

View File

@@ -0,0 +1,27 @@
package de.oaa.xxx.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Component;
import java.time.Duration;
@Component
public class CookieFactory {
private final boolean secure;
public CookieFactory(@Value("${app.cookie.secure:true}") boolean secure) {
this.secure = secure;
}
public ResponseCookie jwtCookie(String token, Duration maxAge) {
return ResponseCookie.from("jwt", token)
.httpOnly(true)
.secure(secure)
.sameSite("Strict")
.path("/")
.maxAge(maxAge)
.build();
}
}

View File

@@ -18,9 +18,11 @@ import java.util.Collections;
public class JwtFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final TokenBlacklistService tokenBlacklist;
public JwtFilter(JwtService jwtService) {
public JwtFilter(JwtService jwtService, TokenBlacklistService tokenBlacklist) {
this.jwtService = jwtService;
this.tokenBlacklist = tokenBlacklist;
}
@Override
@@ -32,10 +34,13 @@ public class JwtFilter extends OncePerRequestFilter {
if ("jwt".equals(cookie.getName())) {
try {
Claims claims = jwtService.validateAndGetClaims(cookie.getValue());
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
claims.getSubject(), null, Collections.emptyList()
);
SecurityContextHolder.getContext().setAuthentication(auth);
String jti = claims.getId();
if (jti == null || !tokenBlacklist.isBlacklisted(jti)) {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
claims.getSubject(), null, Collections.emptyList()
);
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (Exception e) {
// Ungültiger oder abgelaufener Token ohne Authentifizierung weiter
}

View File

@@ -10,6 +10,7 @@ import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Date;
import java.util.UUID;
@Service
public class JwtService {
@@ -33,12 +34,17 @@ public class JwtService {
return Jwts.builder()
.subject(email)
.claim("name", name)
.id(UUID.randomUUID().toString())
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + EXPIRATION_MS))
.signWith(privateKey)
.compact();
}
public long getExpirationMs() {
return EXPIRATION_MS;
}
public Claims validateAndGetClaims(String token) {
return Jwts.parser()
.verifyWith(publicKey)

View File

@@ -0,0 +1,70 @@
package de.oaa.xxx.config;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class RateLimitFilter extends OncePerRequestFilter {
private static final int MAX_REQUESTS = 10;
private static final long WINDOW_MS = 60_000;
private static final String[] RATE_LIMITED_PATHS = {
"/login", "/registration", "/password-reset"
};
private record Window(AtomicInteger count, long startMs) {}
private final Map<String, Window> windows = new ConcurrentHashMap<>();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String path = request.getRequestURI();
boolean isRateLimited = false;
for (String p : RATE_LIMITED_PATHS) {
if (path.equals(p) || path.startsWith(p + "/")) {
isRateLimited = true;
break;
}
}
if (isRateLimited) {
String ip = getClientIp(request);
String key = ip + ":" + path;
long now = System.currentTimeMillis();
Window window = windows.compute(key, (k, w) -> {
if (w == null || now - w.startMs() > WINDOW_MS) {
return new Window(new AtomicInteger(1), now);
}
w.count().incrementAndGet();
return w;
});
if (window.count().get() > MAX_REQUESTS) {
response.setStatus(429);
response.getWriter().write("Too many requests");
return;
}
}
chain.doFilter(request, response);
}
private String getClientIp(HttpServletRequest request) {
String xff = request.getHeader("X-Forwarded-For");
if (xff != null && !xff.isBlank()) {
return xff.split(",")[0].trim();
}
return request.getRemoteAddr();
}
}

View File

@@ -71,6 +71,10 @@ public class SecurityConfig {
.requestMatchers("/games/chastity/joinlock.html").authenticated()
.requestMatchers("/community/benachrichtigungen.html").authenticated()
.requestMatchers("/community/abonnements.html").authenticated()
.requestMatchers("/dating/dating.html").authenticated()
.requestMatchers("/dating/besucher.html").authenticated()
.requestMatchers("/dating/likes.html").authenticated()
.requestMatchers("/dating/matches.html").authenticated()
.requestMatchers("/community/locations.html").authenticated()
.requestMatchers("/community/location-detail.html").authenticated()
.requestMatchers("/community/events.html").authenticated()
@@ -80,7 +84,6 @@ public class SecurityConfig {
.requestMatchers("/notifications/**").authenticated()
.requestMatchers("/events/**").authenticated()
.requestMatchers("/*.html").permitAll()
.requestMatchers("/**/*.html").permitAll()
.requestMatchers("/help/*.html").permitAll()
.requestMatchers("/css/**").permitAll()
.requestMatchers("/js/**").permitAll()

View File

@@ -0,0 +1,34 @@
package de.oaa.xxx.config;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class TokenBlacklistService {
// jti -> Ablaufzeit in ms
private final Map<String, Long> blacklist = new ConcurrentHashMap<>();
public void blacklist(String jti, long expiryMs) {
blacklist.put(jti, expiryMs);
}
public boolean isBlacklisted(String jti) {
Long expiry = blacklist.get(jti);
if (expiry == null) return false;
if (System.currentTimeMillis() > expiry) {
blacklist.remove(jti);
return false;
}
return true;
}
@Scheduled(fixedDelay = 3_600_000)
public void cleanup() {
long now = System.currentTimeMillis();
blacklist.entrySet().removeIf(e -> now > e.getValue());
}
}

View File

@@ -1,23 +1,29 @@
package de.oaa.xxx.emailchange;
import java.io.IOException;
import java.security.Principal;
import java.util.UUID;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import de.oaa.xxx.config.CookieFactory;
import de.oaa.xxx.mail.Email;
import de.oaa.xxx.mail.MailService;
import de.oaa.xxx.mail.MailTemplateService;
import de.oaa.xxx.registration.RegistrationRepository;
import de.oaa.xxx.user.UserRepository;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.security.Principal;
import java.util.UUID;
import java.util.regex.Pattern;
@RestController
@RequestMapping("/email-change")
@@ -34,17 +40,20 @@ public class EmailChangeController {
private final RegistrationRepository registrationRepository;
private final MailService mailService;
private final MailTemplateService mailTemplateService;
private final CookieFactory cookieFactory;
public EmailChangeController(EmailChangeRepository emailChangeRepository,
UserRepository userRepository,
RegistrationRepository registrationRepository,
MailService mailService,
MailTemplateService mailTemplateService) {
MailTemplateService mailTemplateService,
CookieFactory cookieFactory) {
this.emailChangeRepository = emailChangeRepository;
this.userRepository = userRepository;
this.registrationRepository = registrationRepository;
this.mailService = mailService;
this.mailTemplateService = mailTemplateService;
this.cookieFactory = cookieFactory;
}
record EmailChangeRequest(String newEmail) {}
@@ -113,13 +122,7 @@ public class EmailChangeController {
emailChangeRepository.delete(entity.get());
// Clear JWT cookie so user must log in with new email
ResponseCookie cookie = ResponseCookie.from("jwt", "")
.httpOnly(true)
.sameSite("Strict")
.path("/")
.maxAge(0)
.build();
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
response.addHeader(HttpHeaders.SET_COOKIE, cookieFactory.jwtCookie("", java.time.Duration.ZERO).toString());
response.sendRedirect("/login.html?emailChanged=1");
}
}

View File

@@ -264,7 +264,8 @@ public class CardLockController {
}
}
return ResponseEntity.ok(Map.of("lockId", lock.getLockId().toString(), "unlockCode", lock.getUnlockCode(),
return ResponseEntity.ok(Map.of("lockId", lock.getLockId().toString(),
"unlockCode", lock.getUnlockCode() != null ? lock.getUnlockCode() : "",
"keyholderPending", keyholderPending));
}
@@ -408,6 +409,26 @@ public class CardLockController {
return ResponseEntity.ok(Map.of("lockId", activeLockId.get().toString()));
}
@DeleteMapping("/mylock")
@Transactional
public ResponseEntity<Void> deleteMyActiveLock(Principal principal) {
UUID myId = userService.requireUser(principal).getUserId();
var lockOpt = cardlockRepository.findByLockee(myId).stream()
.filter(l -> l.getStartTime() != null && l.getUnlockTime() == null)
.findFirst();
if (lockOpt.isEmpty())
return ResponseEntity.noContent().build();
var l = lockOpt.get();
CardLockService service = cardLockServiceFactory.create(l);
service.unlock(l.getUnlockCode());
var verifications = verificationRepository.findByLockId(l.getLockId());
verifications.forEach(v -> verificationVoteRepository.deleteAllByVerificationId(v.getDisplayId()));
verificationRepository.deleteAll(verifications);
invitationRepository.deleteByLockId(l.getLockId());
cardlockRepository.deleteById(l.getLockId());
return ResponseEntity.noContent().build();
}
@GetMapping("/cardlock/{lockId}")
public ResponseEntity<Map<String, Object>> getLock(@PathVariable("lockId") UUID lockId, Principal principal) {
UUID myId = userService.requireUser(principal).getUserId();

View File

@@ -189,6 +189,14 @@ public class TimeLockController {
// ── State abrufen ────────────────────────────────────────────────────────────
@GetMapping("/timelock/mylock")
public ResponseEntity<Map<String, Object>> getMyActiveTimeLock(Principal principal) {
UUID myId = userService.requireUser(principal).getUserId();
return timeLockRepository.findFirstByLockeeAndStartTimeIsNotNullAndUnlockTimeIsNull(myId)
.map(l -> ResponseEntity.ok(Map.<String, Object>of("lockId", l.getLockId().toString())))
.orElse(ResponseEntity.noContent().build());
}
@GetMapping("/timelock/{lockId}")
@Transactional
public ResponseEntity<Map<String, Object>> getTimeLock(@PathVariable("lockId") UUID lockId, Principal principal) {

View File

@@ -1,56 +0,0 @@
package de.oaa.xxx.games.chastity.ttlock;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import de.oaa.xxx.games.chastity.ttlock.TTLockService.TTLockDetailResponse;
@RestController
@RequestMapping("/ttlock")
public class TTLockTest {
private final TTAuthService auth;
private final TTLockService lock;
private String clientId = "6e5077a84b6a4e1ba0fb6a8da21c6417";
private String clientSecret = "a2c1d68c7905d52584fc29028937db11";
private String username= "mario.stoermer@proton.me";
private String password = "knall666.Halla";
private int lockId = 30158446;
public TTLockTest(TTAuthService auth, TTLockService lock) {
this.auth = auth;
this.lock = lock;
}
@GetMapping("/details")
public ResponseEntity<TTLockDetailResponse> details() {
String md5Hex = org.apache.commons.codec.digest.DigestUtils.md5Hex(password).toLowerCase();
String token = auth.getAccessToken(clientId, clientSecret, username, md5Hex);
return ResponseEntity.ok(lock.getLockDetail(clientId, token, lockId));
}
@GetMapping("/add/{pin}")
public ResponseEntity<Integer> add(@PathVariable String pin) {
String md5Hex = org.apache.commons.codec.digest.DigestUtils.md5Hex(password).toLowerCase();
String token = auth.getAccessToken(clientId, clientSecret, username, md5Hex);
return ResponseEntity.ok(lock.addCustomPasscode(clientId, token, lockId, pin));
}
@GetMapping("/delete/{id}")
public ResponseEntity<String> remove(@PathVariable Integer id) {
String md5Hex = org.apache.commons.codec.digest.DigestUtils.md5Hex(password).toLowerCase();
String token = auth.getAccessToken(clientId, clientSecret, username, md5Hex);
return ResponseEntity.ok(lock.deleteCustomPasscode(clientId, token, lockId, id));
}
@GetMapping("/delete/all")
public void removeAll() {
String md5Hex = org.apache.commons.codec.digest.DigestUtils.md5Hex(password).toLowerCase();
String token = auth.getAccessToken(clientId, clientSecret, username, md5Hex);
lock.findAndDeleteLocksByName(clientId, token, lockId);
}
}

View File

@@ -1,121 +0,0 @@
1-unlock by app
4-unlock by passcode
5-Rise the lock (for parking lock)
6-Lower the lock (for parking lock)
7-unlock by IC card
8-unlock by fingerprint
9-unlock by wrist strap
10-unlock by Mechanical key
11-lock by app
12-unlock by gateway
29-apply some force on the Lock
30-Door sensor closed
31-Door sensor open
32-open from inside
33-lock by fingerprint
34-lock by passcode
35-lock by IC card
36-lock by Mechanical key
37-Use APP button to control the lock (rise, fall, stop, lock), mostly used for roller shutter door
42-received new local mail
43-received new other cities' mail
44-Tamper alert
45-Auto Lock
46-unlock by unlock key
47-lock by lock key
48-System locked ( Caused by, for example: Using INVALID Passcode/Fingerprint/Card several times)
49-unlock by hotel card
50-Unlocked due to the high temperature
51-Try to unlock with a deleted card
52-Dead lock with APP
53-Dead lock with passcode
54-The car left (for parking lock)
55-Use remote control lock or unlock lock
57-Unlock with QR code success
58-Unlock with QR code failed, it's expired
59-Double locked
60-Cancel double lock
61-Lock with QR code success
62-Lock with QR code failed, the lock is double locked
63-Auto unlock at passage mode
64-Door unclosed alarm
65-Failed to unlock
66-Failed to lock
67-Face unlock success
68-Face unlock failed - door locked from inside
69-Lock with face
71-Face unlock failed - expired or ineffective
75-Unlocked by App granting
76-Unlocked by remote granting
77-Dual authentication Bluetooth unlock verification success, waiting for second user
78-Dual authentication password unlock verification success, waiting for second user
79-Dual authentication fingerprint unlock verification success, waiting for second user
80-Dual authentication IC card unlock verification success, waiting for second user
81-Dual authentication face card unlock verification success, waiting for second user
82-Dual authentication wireless key unlock verification success, waiting for second user
83-Dual authentication palm vein unlock verification success, waiting for second user
84-Palm vein unlock success
85-Palm vein unlock success
86-Lock with palm vein
88-Palm vein unlock failed - expired or ineffective
92-Administrator password to unlock

View File

@@ -449,9 +449,9 @@ public class LocationEventController {
.map(e -> {
var loc = locationById.get(e.getLocationId());
String locName = loc != null ? loc.getName() : "";
double dist = (loc != null && loc.getLat() != null && loc.getLon() != null)
? Math.round(LocationController.haversineKm(refLat, refLon, loc.getLat(), loc.getLon()) * 10.0) / 10.0
: -1;
// double dist = (loc != null && loc.getLat() != null && loc.getLon() != null)
// ? Math.round(LocationController.haversineKm(refLat, refLon, loc.getLat(), loc.getLon()) * 10.0) / 10.0
// : -1;
return toPreview(e, locName, refLat, refLon, myId);
})
.toList();

View File

@@ -1,12 +1,14 @@
package de.oaa.xxx.user;
import de.oaa.xxx.admin.AdminRepository;
import de.oaa.xxx.config.CookieFactory;
import de.oaa.xxx.config.JwtService;
import de.oaa.xxx.config.TokenBlacklistService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.GetMapping;
@@ -18,6 +20,7 @@ import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
import java.time.Duration;
import java.util.Arrays;
import java.util.UUID;
@RestController
@@ -33,13 +36,17 @@ public class LoginController {
private final PasswordEncoder passwordEncoder;
private final AdminRepository adminRepository;
private final UserService userService;
private final TokenBlacklistService tokenBlacklist;
private final CookieFactory cookieFactory;
public LoginController(UserRepository userRepository, JwtService jwtService, PasswordEncoder passwordEncoder, AdminRepository adminRepository, UserService userService) {
public LoginController(UserRepository userRepository, JwtService jwtService, PasswordEncoder passwordEncoder, AdminRepository adminRepository, UserService userService, TokenBlacklistService tokenBlacklist, CookieFactory cookieFactory) {
this.userRepository = userRepository;
this.jwtService = jwtService;
this.passwordEncoder = passwordEncoder;
this.adminRepository = adminRepository;
this.userService = userService;
this.tokenBlacklist = tokenBlacklist;
this.cookieFactory = cookieFactory;
}
@PostMapping
@@ -49,13 +56,7 @@ public class LoginController {
UserEntity user = userOpt.get();
LOGGER.info("User erfolgreich angemeldet: {}", request.email());
String token = jwtService.generateToken(user.getEmail(), user.getName());
ResponseCookie cookie = ResponseCookie.from("jwt", token)
.httpOnly(true)
.sameSite("Strict")
.path("/")
.maxAge(Duration.ofHours(24))
.build();
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
response.addHeader(HttpHeaders.SET_COOKIE, cookieFactory.jwtCookie(token, Duration.ofHours(24)).toString());
User u = user.toUser();
u.setAdmin(adminRepository.existsByUserId(user.getUserId()));
return ResponseEntity.ok(u);
@@ -76,14 +77,22 @@ public class LoginController {
}
@GetMapping("/logout")
public void logout(HttpServletResponse response) throws java.io.IOException {
ResponseCookie cookie = ResponseCookie.from("jwt", "")
.httpOnly(true)
.sameSite("Strict")
.path("/")
.maxAge(0)
.build();
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
public void logout(HttpServletRequest request, HttpServletResponse response) throws java.io.IOException {
if (request.getCookies() != null) {
Arrays.stream(request.getCookies())
.filter(c -> "jwt".equals(c.getName()))
.findFirst()
.ifPresent(c -> {
try {
var claims = jwtService.validateAndGetClaims(c.getValue());
String jti = claims.getId();
if (jti != null) {
tokenBlacklist.blacklist(jti, claims.getExpiration().getTime());
}
} catch (Exception ignored) {}
});
}
response.addHeader(HttpHeaders.SET_COOKIE, cookieFactory.jwtCookie("", Duration.ZERO).toString());
response.sendRedirect("/");
}

View File

@@ -19,6 +19,7 @@ public class User {
private boolean admin;
private String profilePicture;
private LocalDate geburtsdatum;
private LocalDate registrierungsdatum;
private Integer groesse;
private Integer gewicht;
private Geschlecht geschlecht;

View File

@@ -1,5 +1,6 @@
package de.oaa.xxx.user;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.time.LocalDate;
import java.time.Period;
@@ -13,8 +14,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -24,6 +25,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import de.oaa.xxx.config.CookieFactory;
import de.oaa.xxx.games.bdsm.entity.BdsmDefaultsEntity;
import de.oaa.xxx.games.bdsm.repository.BdsmDefaultsRepository;
import de.oaa.xxx.games.chastity.common.BaseLockRepository;
@@ -39,9 +41,6 @@ import de.oaa.xxx.registration.RegistrationRepository;
import de.oaa.xxx.social.entity.MessageCause;
import de.oaa.xxx.social.entity.NotificationPreferenceEntity;
import de.oaa.xxx.social.repository.NotificationPreferenceRepository;
import org.springframework.util.DigestUtils;
import java.nio.charset.StandardCharsets;
@RestController
@RequestMapping("/user")
@@ -60,6 +59,7 @@ public class UserController {
private final TTLockService ttLockService;
private final BaseLockRepository baseLockRepository;
private final BaseLockTemplateRepository baseLockTemplateRepository;
private final CookieFactory cookieFactory;
public UserController(UserRepository userRepository,
RegistrationRepository registrationRepository,
@@ -71,7 +71,8 @@ public class UserController {
TTAuthService ttAuthService,
TTLockService ttLockService,
BaseLockRepository baseLockRepository,
BaseLockTemplateRepository baseLockTemplateRepository) {
BaseLockTemplateRepository baseLockTemplateRepository,
CookieFactory cookieFactory) {
this.userRepository = userRepository;
this.registrationRepository = registrationRepository;
this.notificationPreferenceRepository = notificationPreferenceRepository;
@@ -83,6 +84,7 @@ public class UserController {
this.ttLockService = ttLockService;
this.baseLockRepository = baseLockRepository;
this.baseLockTemplateRepository = baseLockTemplateRepository;
this.cookieFactory = cookieFactory;
}
record ProfilePictureRequest(String picture, String pictureHq) {}
@@ -348,14 +350,8 @@ public class UserController {
userService.deleteAccount(userId, email);
ResponseCookie cookie = ResponseCookie.from("jwt", "")
.httpOnly(true)
.sameSite("Strict")
.path("/")
.maxAge(0)
.build();
return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, cookie.toString())
.header(HttpHeaders.SET_COOKIE, cookieFactory.jwtCookie("", java.time.Duration.ZERO).toString())
.build();
}
@@ -501,6 +497,31 @@ public class UserController {
}
}
record NewMemberDto(UUID userId, String name, String profilePicture,
Integer alter, String geschlecht, String neigung,
String datingStadt, String beschreibung) {}
@GetMapping("/new-members")
public ResponseEntity<List<NewMemberDto>> getNewMembers(Principal principal) {
UUID myId = principal != null ? userService.requireUser(principal).getUserId() : null;
LocalDate since = LocalDate.now().minusDays(14);
List<NewMemberDto> result = userRepository
.findByRegistrierungsdatumAfterOrderByRegistrierungsdatumDesc(since)
.stream()
.filter(u -> !u.getUserId().equals(myId))
.map(u -> new NewMemberDto(
u.getUserId(),
u.getName(),
u.getProfilePictureHq() != null ? u.getProfilePictureHq() : u.getProfilePicture(),
u.getAlter(),
u.getGeschlecht() != null ? u.getGeschlecht().name() : null,
u.getNeigung() != null ? u.getNeigung().name() : null,
u.getDatingStadt(),
u.getBeschreibung()))
.collect(Collectors.toList());
return ResponseEntity.ok(result);
}
record LocationFilterRequest(String filterCity, Double filterLat, Double filterLon, Integer filterMaxDistKm) {}
@PutMapping("/me/location-filter")

View File

@@ -39,6 +39,9 @@ public class UserEntity {
@Column
private LocalDate geburtsdatum;
@Column(nullable = false)
private LocalDate registrierungsdatum;
@Column
private Integer groesse;
@@ -163,6 +166,7 @@ public class UserEntity {
user.setUserId(userId);
user.setProfilePicture(profilePicture);
user.setGeburtsdatum(geburtsdatum);
user.setRegistrierungsdatum(registrierungsdatum);
user.setGroesse(groesse);
user.setGewicht(gewicht);
user.setGeschlecht(geschlecht);

View File

@@ -2,6 +2,7 @@ package de.oaa.xxx.user;
import org.springframework.data.jpa.repository.JpaRepository;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@@ -12,4 +13,5 @@ public interface UserRepository extends JpaRepository<UserEntity, UUID> {
Optional<UserEntity> findByName(String name);
List<UserEntity> findByNameContainingIgnoreCase(String name);
List<UserEntity> findByDatingAktiv(boolean datingAktiv);
List<UserEntity> findByRegistrierungsdatumAfterOrderByRegistrierungsdatumDesc(LocalDate since);
}

View File

@@ -32,6 +32,7 @@ import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.security.Principal;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
@@ -220,6 +221,7 @@ public class UserService {
entity.setName(registration.getName());
entity.setPassword(registration.getPassword());
entity.setGeburtsdatum(registration.getGeburtsdatum());
entity.setRegistrierungsdatum(LocalDate.now());
userRepository.save(entity);
for (MessageCause cause : MessageCause.values()) {