Weiter an den Locations gearbeitet
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
This commit is contained in:
176
src/main/java/de/oaa/xxx/location/LocationAdminController.java
Normal file
176
src/main/java/de/oaa/xxx/location/LocationAdminController.java
Normal file
@@ -0,0 +1,176 @@
|
||||
package de.oaa.xxx.location;
|
||||
|
||||
import de.oaa.xxx.location.entity.LocationAdminEntity;
|
||||
import de.oaa.xxx.location.repository.LocationAdminRepository;
|
||||
import de.oaa.xxx.location.repository.LocationRepository;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
import de.oaa.xxx.user.UserService;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/locations/{locationId}/admins")
|
||||
public class LocationAdminController {
|
||||
|
||||
record AdminDto(UUID userId, String name, String profilePicture, boolean isOwner) {}
|
||||
record AddAdminRequest(UUID userId) {}
|
||||
record TransferOwnerRequest(UUID userId) {}
|
||||
|
||||
private final LocationRepository locationRepo;
|
||||
private final LocationAdminRepository adminRepo;
|
||||
private final UserRepository userRepo;
|
||||
private final UserService userService;
|
||||
|
||||
public LocationAdminController(LocationRepository locationRepo,
|
||||
LocationAdminRepository adminRepo,
|
||||
UserRepository userRepo,
|
||||
UserService userService) {
|
||||
this.locationRepo = locationRepo;
|
||||
this.adminRepo = adminRepo;
|
||||
this.userRepo = userRepo;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
// ── Hilfsmethoden ────────────────────────────────────────────────────────
|
||||
|
||||
private boolean isAdmin(UUID locationId, UUID userId, de.oaa.xxx.location.entity.LocationEntity loc) {
|
||||
return loc.getOwnerId().equals(userId)
|
||||
|| adminRepo.existsByLocationIdAndUserId(locationId, userId);
|
||||
}
|
||||
|
||||
private AdminDto toDto(UUID userId, de.oaa.xxx.location.entity.LocationEntity loc) {
|
||||
return userRepo.findById(userId).map(u -> new AdminDto(
|
||||
u.getUserId(), u.getName(), u.getProfilePicture(),
|
||||
u.getUserId().equals(loc.getOwnerId())))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
// ── Admins auflisten ─────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<AdminDto>> list(
|
||||
@PathVariable UUID locationId,
|
||||
Principal principal) {
|
||||
|
||||
userService.requireUser(principal);
|
||||
var locOpt = locationRepo.findById(locationId);
|
||||
if (locOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
var loc = locOpt.get();
|
||||
|
||||
// Inhaber ist immer erster Eintrag
|
||||
List<AdminDto> admins = new java.util.ArrayList<>();
|
||||
userRepo.findById(loc.getOwnerId()).ifPresent(owner ->
|
||||
admins.add(new AdminDto(owner.getUserId(), owner.getName(), owner.getProfilePicture(), true)));
|
||||
|
||||
adminRepo.findByLocationId(locationId).stream()
|
||||
.filter(a -> !a.getUserId().equals(loc.getOwnerId())) // Inhaber nicht doppelt
|
||||
.map(a -> toDto(a.getUserId(), loc))
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.forEach(admins::add);
|
||||
|
||||
return ResponseEntity.ok(admins);
|
||||
}
|
||||
|
||||
// ── Admin hinzufügen ──────────────────────────────────────────────────────
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<AdminDto> add(
|
||||
@PathVariable UUID locationId,
|
||||
@RequestBody AddAdminRequest req,
|
||||
Principal principal) {
|
||||
|
||||
UUID myId = userService.requireUser(principal).getUserId();
|
||||
var locOpt = locationRepo.findById(locationId);
|
||||
if (locOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
var loc = locOpt.get();
|
||||
|
||||
if (!isAdmin(locationId, myId, loc)) return ResponseEntity.status(403).build();
|
||||
if (req.userId() == null || !userRepo.existsById(req.userId()))
|
||||
return ResponseEntity.badRequest().build();
|
||||
|
||||
// Inhaber muss nicht eingetragen werden
|
||||
if (req.userId().equals(loc.getOwnerId()))
|
||||
return ResponseEntity.badRequest().build();
|
||||
|
||||
// Bereits Admin?
|
||||
if (adminRepo.existsByLocationIdAndUserId(locationId, req.userId()))
|
||||
return ResponseEntity.status(409).build();
|
||||
|
||||
LocationAdminEntity entity = new LocationAdminEntity();
|
||||
entity.setAdminId(UUID.randomUUID());
|
||||
entity.setLocationId(locationId);
|
||||
entity.setUserId(req.userId());
|
||||
entity.setAddedAt(LocalDateTime.now());
|
||||
adminRepo.save(entity);
|
||||
|
||||
return ResponseEntity.status(201).body(toDto(req.userId(), loc));
|
||||
}
|
||||
|
||||
// ── Admin entfernen ───────────────────────────────────────────────────────
|
||||
|
||||
@Transactional
|
||||
@DeleteMapping("/{userId}")
|
||||
public ResponseEntity<Void> remove(
|
||||
@PathVariable UUID locationId,
|
||||
@PathVariable UUID userId,
|
||||
Principal principal) {
|
||||
|
||||
UUID myId = userService.requireUser(principal).getUserId();
|
||||
var locOpt = locationRepo.findById(locationId);
|
||||
if (locOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
var loc = locOpt.get();
|
||||
|
||||
if (!isAdmin(locationId, myId, loc)) return ResponseEntity.status(403).build();
|
||||
|
||||
// Inhaber darf nicht entfernt werden
|
||||
if (userId.equals(loc.getOwnerId())) return ResponseEntity.status(403).build();
|
||||
|
||||
adminRepo.deleteByLocationIdAndUserId(locationId, userId);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
// ── Inhaberwechsel ────────────────────────────────────────────────────────
|
||||
|
||||
@Transactional
|
||||
@PutMapping("/transfer-owner")
|
||||
public ResponseEntity<Map<String, Object>> transferOwner(
|
||||
@PathVariable UUID locationId,
|
||||
@RequestBody TransferOwnerRequest req,
|
||||
Principal principal) {
|
||||
|
||||
UUID myId = userService.requireUser(principal).getUserId();
|
||||
var locOpt = locationRepo.findById(locationId);
|
||||
if (locOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
var loc = locOpt.get();
|
||||
|
||||
// Nur der aktuelle Inhaber darf übertragen
|
||||
if (!loc.getOwnerId().equals(myId)) return ResponseEntity.status(403).build();
|
||||
if (req.userId() == null || !userRepo.existsById(req.userId()))
|
||||
return ResponseEntity.badRequest().build();
|
||||
if (req.userId().equals(myId)) return ResponseEntity.badRequest().build();
|
||||
|
||||
// Neuer Inhaber als Admin eintragen (falls noch nicht), alter Inhaber wird normaler Admin
|
||||
if (!adminRepo.existsByLocationIdAndUserId(locationId, myId)) {
|
||||
LocationAdminEntity a = new LocationAdminEntity();
|
||||
a.setAdminId(UUID.randomUUID());
|
||||
a.setLocationId(locationId);
|
||||
a.setUserId(myId);
|
||||
a.setAddedAt(LocalDateTime.now());
|
||||
adminRepo.save(a);
|
||||
}
|
||||
// Neuen Inhaber aus Admin-Liste entfernen (er ist jetzt Owner)
|
||||
adminRepo.deleteByLocationIdAndUserId(locationId, req.userId());
|
||||
|
||||
loc.setOwnerId(req.userId());
|
||||
locationRepo.save(loc);
|
||||
|
||||
return ResponseEntity.ok(Map.of("newOwnerId", req.userId()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package de.oaa.xxx.location;
|
||||
|
||||
import de.oaa.xxx.location.entity.LocationEntity;
|
||||
import de.oaa.xxx.location.repository.LocationInboxLockRepository;
|
||||
import de.oaa.xxx.location.repository.LocationRepository;
|
||||
import de.oaa.xxx.social.entity.MessageEntity;
|
||||
import de.oaa.xxx.social.repository.MessageRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
public class LocationChatCleanupService {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(LocationChatCleanupService.class);
|
||||
|
||||
private final LocationRepository locationRepo;
|
||||
private final MessageRepository messageRepo;
|
||||
private final LocationInboxLockRepository lockRepo;
|
||||
|
||||
public LocationChatCleanupService(LocationRepository locationRepo,
|
||||
MessageRepository messageRepo,
|
||||
LocationInboxLockRepository lockRepo) {
|
||||
this.locationRepo = locationRepo;
|
||||
this.messageRepo = messageRepo;
|
||||
this.lockRepo = lockRepo;
|
||||
}
|
||||
|
||||
/** Täglich um 03:00 Uhr: Location-Chats löschen, die seit mehr als einem Monat inaktiv sind. */
|
||||
@Scheduled(cron = "0 0 3 * * *")
|
||||
@Transactional
|
||||
public void cleanupInactiveChats() {
|
||||
LocalDateTime cutoff = LocalDateTime.now().minusMonths(1);
|
||||
List<LocationEntity> locations = locationRepo.findByVirtualUserIdIsNotNull();
|
||||
int deleted = 0;
|
||||
|
||||
for (LocationEntity loc : locations) {
|
||||
UUID virtualId = loc.getVirtualUserId();
|
||||
|
||||
// Alle Nachrichten dieser Location (in beide Richtungen)
|
||||
List<MessageEntity> allMessages = messageRepo.findAllByUser(virtualId);
|
||||
if (allMessages.isEmpty()) continue;
|
||||
|
||||
// Neueste Nachricht pro Gesprächspartner (Besucher)
|
||||
Map<UUID, LocalDateTime> latestByVisitor = new HashMap<>();
|
||||
for (MessageEntity m : allMessages) {
|
||||
UUID visitor = m.getSenderId().equals(virtualId) ? m.getReceiverId() : m.getSenderId();
|
||||
latestByVisitor.merge(visitor, m.getSentAt(),
|
||||
(a, b) -> a.isAfter(b) ? a : b);
|
||||
}
|
||||
|
||||
for (Map.Entry<UUID, LocalDateTime> entry : latestByVisitor.entrySet()) {
|
||||
if (entry.getValue().isBefore(cutoff)) {
|
||||
UUID visitorId = entry.getKey();
|
||||
messageRepo.deleteConversation(virtualId, visitorId);
|
||||
lockRepo.findByLocationIdAndVisitorId(loc.getLocationId(), visitorId)
|
||||
.ifPresent(lockRepo::delete);
|
||||
deleted++;
|
||||
LOGGER.info("Inaktiver Location-Chat gelöscht: location={} visitor={}", loc.getLocationId(), visitorId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (deleted > 0) {
|
||||
LOGGER.info("Location-Chat-Cleanup abgeschlossen: {} Konversation(en) gelöscht.", deleted);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
package de.oaa.xxx.location;
|
||||
|
||||
import de.oaa.xxx.location.entity.*;
|
||||
import de.oaa.xxx.location.entity.LocationInboxLockEntity;
|
||||
import de.oaa.xxx.location.repository.*;
|
||||
import de.oaa.xxx.location.repository.LocationInboxLockRepository;
|
||||
import de.oaa.xxx.social.SseService;
|
||||
import de.oaa.xxx.social.entity.MessageEntity;
|
||||
import de.oaa.xxx.social.repository.MessageRepository;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
import de.oaa.xxx.user.UserService;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.slf4j.Logger;
|
||||
@@ -12,6 +18,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
import java.security.Principal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.Collections;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@@ -22,13 +29,20 @@ public class LocationController {
|
||||
private static final int MAX_GALLERY_IMAGES = 20;
|
||||
private static final int MAX_BATCH_SIZE = 50;
|
||||
|
||||
private final LocationRepository locationRepo;
|
||||
private final LocationImageRepository imageRepo;
|
||||
private static final int LOCK_TIMEOUT_MINUTES = 60;
|
||||
|
||||
private final LocationRepository locationRepo;
|
||||
private final LocationImageRepository imageRepo;
|
||||
private final LocationOpeningHoursRepository hoursRepo;
|
||||
private final LocationEventRepository eventRepo;
|
||||
private final LocationEventRepository eventRepo;
|
||||
private final LocationEventAttendeeRepository attendeeRepo;
|
||||
private final LocationFollowRepository followRepo;
|
||||
private final UserService userService;
|
||||
private final LocationFollowRepository followRepo;
|
||||
private final LocationAdminRepository adminRepo;
|
||||
private final UserRepository userRepo;
|
||||
private final UserService userService;
|
||||
private final MessageRepository messageRepo;
|
||||
private final SseService sseService;
|
||||
private final LocationInboxLockRepository lockRepo;
|
||||
|
||||
public LocationController(LocationRepository locationRepo,
|
||||
LocationImageRepository imageRepo,
|
||||
@@ -36,26 +50,38 @@ public class LocationController {
|
||||
LocationEventRepository eventRepo,
|
||||
LocationEventAttendeeRepository attendeeRepo,
|
||||
LocationFollowRepository followRepo,
|
||||
UserService userService) {
|
||||
LocationAdminRepository adminRepo,
|
||||
UserRepository userRepo,
|
||||
UserService userService,
|
||||
MessageRepository messageRepo,
|
||||
SseService sseService,
|
||||
LocationInboxLockRepository lockRepo) {
|
||||
this.locationRepo = locationRepo;
|
||||
this.imageRepo = imageRepo;
|
||||
this.hoursRepo = hoursRepo;
|
||||
this.eventRepo = eventRepo;
|
||||
this.attendeeRepo = attendeeRepo;
|
||||
this.followRepo = followRepo;
|
||||
this.adminRepo = adminRepo;
|
||||
this.userRepo = userRepo;
|
||||
this.userService = userService;
|
||||
this.messageRepo = messageRepo;
|
||||
this.sseService = sseService;
|
||||
this.lockRepo = lockRepo;
|
||||
}
|
||||
|
||||
// ── DTOs ─────────────────────────────────────────────────────────────────
|
||||
|
||||
record IdsResult(List<UUID> ids, int total) {}
|
||||
|
||||
record LocationPreviewDto(UUID locationId, String name, String profilePictureLq, double distanzKm) {}
|
||||
record LocationPreviewDto(UUID locationId, String name, String profilePictureHq, double distanzKm) {}
|
||||
|
||||
record OpeningHourDto(int dayOfWeek, String openTime, String closeTime, boolean closed) {}
|
||||
|
||||
record GalleryImageDto(UUID imageId, String imageData) {}
|
||||
|
||||
record AdminDto(UUID userId, String name, String profilePicture, boolean isOwner) {}
|
||||
|
||||
record LocationDetailDto(
|
||||
UUID locationId,
|
||||
UUID ownerId,
|
||||
@@ -71,7 +97,10 @@ public class LocationController {
|
||||
LocalDateTime createdAt,
|
||||
List<GalleryImageDto> gallery,
|
||||
List<OpeningHourDto> openingHours,
|
||||
boolean following
|
||||
boolean following,
|
||||
List<AdminDto> admins,
|
||||
boolean isAdmin,
|
||||
UUID virtualUserId
|
||||
) {}
|
||||
|
||||
record CreateRequest(
|
||||
@@ -99,6 +128,21 @@ public class LocationController {
|
||||
|
||||
record GalleryUploadRequest(String imageData) {}
|
||||
|
||||
record LocationVirtualInfoDto(UUID locationId, UUID virtualUserId, String name,
|
||||
String profilePictureLq, String profilePictureHq) {}
|
||||
|
||||
/** Löst eine virtuelle Benutzer-ID zur Location-Info auf (für Nachrichtenfenster) */
|
||||
@GetMapping("/virtual/{virtualUserId}")
|
||||
public ResponseEntity<LocationVirtualInfoDto> getByVirtualUserId(
|
||||
@PathVariable UUID virtualUserId, Principal principal) {
|
||||
userService.requireUser(principal);
|
||||
return locationRepo.findByVirtualUserId(virtualUserId)
|
||||
.map(l -> ResponseEntity.ok(new LocationVirtualInfoDto(
|
||||
l.getLocationId(), l.getVirtualUserId(), l.getName(),
|
||||
l.getProfilePictureLq(), l.getProfilePictureHq())))
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
// ── Suche / IDs ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -147,7 +191,7 @@ public class LocationController {
|
||||
.map(l -> new LocationPreviewDto(
|
||||
l.getLocationId(),
|
||||
l.getName(),
|
||||
l.getProfilePictureLq(),
|
||||
l.getProfilePictureHq(),
|
||||
l.getLat() != null && l.getLon() != null
|
||||
? Math.round(haversineKm(refLat, refLon, l.getLat(), l.getLon()) * 10.0) / 10.0
|
||||
: -1))
|
||||
@@ -207,6 +251,7 @@ public class LocationController {
|
||||
loc.setCity(req.city());
|
||||
loc.setOwnershipConfirmed(req.ownershipConfirmed());
|
||||
loc.setCreatedAt(LocalDateTime.now());
|
||||
loc.setVirtualUserId(UUID.randomUUID());
|
||||
locationRepo.save(loc);
|
||||
|
||||
LOGGER.info("User {} hat Location {} angelegt", myId, loc.getLocationId());
|
||||
@@ -257,6 +302,7 @@ public class LocationController {
|
||||
imageRepo.deleteByLocationId(locationId);
|
||||
hoursRepo.deleteByLocationId(locationId);
|
||||
followRepo.deleteByLocationId(locationId);
|
||||
adminRepo.deleteByLocationId(locationId);
|
||||
locationRepo.deleteById(locationId);
|
||||
|
||||
LOGGER.info("User {} hat Location {} gelöscht", myId, locationId);
|
||||
@@ -398,6 +444,12 @@ public class LocationController {
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
private LocationDetailDto toDetail(LocationEntity l, UUID myId) {
|
||||
// Lazy-Init virtualUserId für Bestandsdaten ohne virtuelle ID
|
||||
if (l.getVirtualUserId() == null) {
|
||||
l.setVirtualUserId(UUID.randomUUID());
|
||||
locationRepo.save(l);
|
||||
}
|
||||
|
||||
List<GalleryImageDto> gallery = imageRepo.findByLocationIdOrderByUploadedAtAsc(l.getLocationId()).stream()
|
||||
.map(i -> new GalleryImageDto(i.getImageId(), i.getImageData()))
|
||||
.toList();
|
||||
@@ -405,12 +457,241 @@ public class LocationController {
|
||||
.map(h -> new OpeningHourDto(h.getDayOfWeek(), h.getOpenTime(), h.getCloseTime(), h.isClosed()))
|
||||
.toList();
|
||||
boolean following = followRepo.findByUserIdAndLocationId(myId, l.getLocationId()).isPresent();
|
||||
boolean isAdmin = l.getOwnerId().equals(myId)
|
||||
|| adminRepo.existsByLocationIdAndUserId(l.getLocationId(), myId);
|
||||
|
||||
// Inhaber zuerst, dann weitere Admins
|
||||
List<AdminDto> admins = new ArrayList<>();
|
||||
userRepo.findById(l.getOwnerId()).ifPresent(owner ->
|
||||
admins.add(new AdminDto(owner.getUserId(), owner.getName(), owner.getProfilePicture(), true)));
|
||||
adminRepo.findByLocationId(l.getLocationId()).stream()
|
||||
.filter(a -> !a.getUserId().equals(l.getOwnerId()))
|
||||
.forEach(a -> userRepo.findById(a.getUserId()).ifPresent(u ->
|
||||
admins.add(new AdminDto(u.getUserId(), u.getName(), u.getProfilePicture(), false))));
|
||||
|
||||
return new LocationDetailDto(
|
||||
l.getLocationId(), l.getOwnerId(), l.getName(), l.getDescription(),
|
||||
l.getProfilePictureHq(), l.getProfilePictureLq(),
|
||||
l.getLat(), l.getLon(), l.getStreet(), l.getCity(),
|
||||
l.isOwnershipConfirmed(), l.getCreatedAt(),
|
||||
gallery, hours, following);
|
||||
gallery, hours, following, admins, isAdmin, l.getVirtualUserId());
|
||||
}
|
||||
|
||||
// ── Location-Posteingang (Admin) ─────────────────────────────────────────────
|
||||
|
||||
record InboxSummaryDto(UUID senderId, String senderName, String senderPicture,
|
||||
String lastMessage, LocalDateTime sentAt, long unreadCount) {}
|
||||
|
||||
record InboxConversationDto(
|
||||
List<de.oaa.xxx.social.dto.MessageDto> messages,
|
||||
boolean canReply,
|
||||
boolean lockedByMe,
|
||||
String lockedByName // null wenn frei oder von mir gesperrt
|
||||
) {}
|
||||
|
||||
record ReplyRequest(String text) {}
|
||||
|
||||
/** Alle Konversationen, die Besucher mit dieser Location geführt haben */
|
||||
@GetMapping("/{locationId}/inbox")
|
||||
public ResponseEntity<List<InboxSummaryDto>> getInbox(
|
||||
@PathVariable UUID locationId, Principal principal) {
|
||||
|
||||
UUID myId = userService.requireUser(principal).getUserId();
|
||||
var locOpt = locationRepo.findById(locationId);
|
||||
if (locOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
LocationEntity loc = locOpt.get();
|
||||
if (!loc.getOwnerId().equals(myId) && !adminRepo.existsByLocationIdAndUserId(locationId, myId)) {
|
||||
return ResponseEntity.status(403).build();
|
||||
}
|
||||
UUID virtualId = loc.getVirtualUserId();
|
||||
if (virtualId == null) return ResponseEntity.ok(List.of());
|
||||
|
||||
List<MessageEntity> allMessages = messageRepo.findAllByUser(virtualId);
|
||||
|
||||
// Neueste Nachricht pro Gesprächspartner (Besucher)
|
||||
Map<UUID, MessageEntity> latestByPartner = new LinkedHashMap<>();
|
||||
for (MessageEntity m : allMessages) {
|
||||
UUID partnerId = m.getSenderId().equals(virtualId) ? m.getReceiverId() : m.getSenderId();
|
||||
latestByPartner.putIfAbsent(partnerId, m);
|
||||
}
|
||||
|
||||
List<InboxSummaryDto> summaries = new ArrayList<>();
|
||||
for (Map.Entry<UUID, MessageEntity> entry : latestByPartner.entrySet()) {
|
||||
UUID partnerId = entry.getKey();
|
||||
MessageEntity lastMsg = entry.getValue();
|
||||
var userOpt = userRepo.findById(partnerId);
|
||||
if (userOpt.isEmpty()) continue;
|
||||
var user = userOpt.get();
|
||||
long unread = allMessages.stream()
|
||||
.filter(m -> m.getSenderId().equals(partnerId)
|
||||
&& m.getReceiverId().equals(virtualId)
|
||||
&& m.getReadAt() == null)
|
||||
.count();
|
||||
String preview = lastMsg.getText().startsWith("data:image/")
|
||||
? "📷 Bild"
|
||||
: lastMsg.getText().substring(0, Math.min(80, lastMsg.getText().length()));
|
||||
summaries.add(new InboxSummaryDto(partnerId, user.getName(), user.getProfilePicture(),
|
||||
preview, lastMsg.getSentAt(), unread));
|
||||
}
|
||||
return ResponseEntity.ok(summaries);
|
||||
}
|
||||
|
||||
/** Konversation zwischen Location und einem Besucher inkl. Lock-Status (Admin-Sicht) */
|
||||
@GetMapping("/{locationId}/inbox/{userId}")
|
||||
public ResponseEntity<InboxConversationDto> getInboxConversation(
|
||||
@PathVariable UUID locationId,
|
||||
@PathVariable UUID userId,
|
||||
Principal principal) {
|
||||
|
||||
UUID myId = userService.requireUser(principal).getUserId();
|
||||
var locOpt = locationRepo.findById(locationId);
|
||||
if (locOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
LocationEntity loc = locOpt.get();
|
||||
if (!loc.getOwnerId().equals(myId) && !adminRepo.existsByLocationIdAndUserId(locationId, myId)) {
|
||||
return ResponseEntity.status(403).build();
|
||||
}
|
||||
UUID virtualId = loc.getVirtualUserId();
|
||||
if (virtualId == null) return ResponseEntity.ok(new InboxConversationDto(List.of(), true, false, null));
|
||||
|
||||
List<MessageEntity> messages = new ArrayList<>(
|
||||
messageRepo.findConversation(virtualId, userId, org.springframework.data.domain.PageRequest.of(0, 100)));
|
||||
Collections.reverse(messages);
|
||||
|
||||
messageRepo.markAsRead(virtualId, userId, LocalDateTime.now());
|
||||
|
||||
List<de.oaa.xxx.social.dto.MessageDto> dtos = messages.stream()
|
||||
.map(m -> {
|
||||
String senderName = userRepo.findById(m.getSenderId())
|
||||
.map(u -> u.getName())
|
||||
.orElse(loc.getName());
|
||||
return new de.oaa.xxx.social.dto.MessageDto(
|
||||
m.getMessageId(), m.getSenderId(), senderName,
|
||||
m.getReceiverId(), m.getText(), m.getSentAt(), m.getReadAt() != null);
|
||||
})
|
||||
.toList();
|
||||
|
||||
// Lock-Status ermitteln
|
||||
var lockInfo = resolveLockStatus(locationId, userId, myId);
|
||||
|
||||
return ResponseEntity.ok(new InboxConversationDto(dtos, lockInfo[0].equals("true"), lockInfo[1].equals("true"), lockInfo[2]));
|
||||
}
|
||||
|
||||
/** Sperre für eine Konversation anfordern (Admin beginnt zu antworten) */
|
||||
@PostMapping("/{locationId}/inbox/{userId}/lock")
|
||||
public ResponseEntity<Map<String, Object>> acquireLock(
|
||||
@PathVariable UUID locationId,
|
||||
@PathVariable UUID userId,
|
||||
Principal principal) {
|
||||
|
||||
UUID myId = userService.requireUser(principal).getUserId();
|
||||
var locOpt = locationRepo.findById(locationId);
|
||||
if (locOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
LocationEntity loc = locOpt.get();
|
||||
if (!loc.getOwnerId().equals(myId) && !adminRepo.existsByLocationIdAndUserId(locationId, myId)) {
|
||||
return ResponseEntity.status(403).build();
|
||||
}
|
||||
|
||||
var existingOpt = lockRepo.findByLocationIdAndVisitorId(locationId, userId);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
if (existingOpt.isPresent()) {
|
||||
LocationInboxLockEntity lock = existingOpt.get();
|
||||
boolean expired = lock.getLockedAt().isBefore(now.minusMinutes(LOCK_TIMEOUT_MINUTES));
|
||||
if (!expired && !lock.getLockedByUserId().equals(myId)) {
|
||||
// Aktiv gesperrt durch einen anderen Admin
|
||||
String lockerName = userRepo.findById(lock.getLockedByUserId())
|
||||
.map(u -> u.getName()).orElse("einem anderen Admin");
|
||||
return ResponseEntity.status(409).body(Map.of("lockedByName", lockerName));
|
||||
}
|
||||
// Abgelaufen oder bereits meine Sperre → erneuern
|
||||
lock.setLockedByUserId(myId);
|
||||
lock.setLockedAt(now);
|
||||
lockRepo.save(lock);
|
||||
} else {
|
||||
LocationInboxLockEntity lock = new LocationInboxLockEntity();
|
||||
lock.setLockId(UUID.randomUUID());
|
||||
lock.setLocationId(locationId);
|
||||
lock.setVisitorId(userId);
|
||||
lock.setLockedByUserId(myId);
|
||||
lock.setLockedAt(now);
|
||||
lockRepo.save(lock);
|
||||
}
|
||||
return ResponseEntity.ok(Map.of("acquired", true));
|
||||
}
|
||||
|
||||
/** Antwort als Location an einen Besucher senden (mit Lock-Prüfung) */
|
||||
@PostMapping("/{locationId}/inbox/{userId}/reply")
|
||||
public ResponseEntity<Map<String, Object>> replyAsLocation(
|
||||
@PathVariable UUID locationId,
|
||||
@PathVariable UUID userId,
|
||||
@RequestBody ReplyRequest req,
|
||||
Principal principal) {
|
||||
|
||||
UUID myId = userService.requireUser(principal).getUserId();
|
||||
var locOpt = locationRepo.findById(locationId);
|
||||
if (locOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
LocationEntity loc = locOpt.get();
|
||||
if (!loc.getOwnerId().equals(myId) && !adminRepo.existsByLocationIdAndUserId(locationId, myId)) {
|
||||
return ResponseEntity.status(403).build();
|
||||
}
|
||||
if (req.text() == null || req.text().isBlank()) return ResponseEntity.badRequest().build();
|
||||
UUID virtualId = loc.getVirtualUserId();
|
||||
if (virtualId == null) return ResponseEntity.badRequest().build();
|
||||
|
||||
// Lock prüfen
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
var existingLockOpt = lockRepo.findByLocationIdAndVisitorId(locationId, userId);
|
||||
if (existingLockOpt.isPresent()) {
|
||||
LocationInboxLockEntity lock = existingLockOpt.get();
|
||||
boolean expired = lock.getLockedAt().isBefore(now.minusMinutes(LOCK_TIMEOUT_MINUTES));
|
||||
if (!expired && !lock.getLockedByUserId().equals(myId)) {
|
||||
String lockerName = userRepo.findById(lock.getLockedByUserId())
|
||||
.map(u -> u.getName()).orElse("einem anderen Admin");
|
||||
return ResponseEntity.status(409).body(Map.of("lockedByName", lockerName));
|
||||
}
|
||||
// Abgelaufen oder meine Sperre → erneuern
|
||||
lock.setLockedByUserId(myId);
|
||||
lock.setLockedAt(now);
|
||||
lockRepo.save(lock);
|
||||
} else {
|
||||
// Noch keine Sperre → automatisch erwerben
|
||||
LocationInboxLockEntity lock = new LocationInboxLockEntity();
|
||||
lock.setLockId(UUID.randomUUID());
|
||||
lock.setLocationId(locationId);
|
||||
lock.setVisitorId(userId);
|
||||
lock.setLockedByUserId(myId);
|
||||
lock.setLockedAt(now);
|
||||
lockRepo.save(lock);
|
||||
}
|
||||
|
||||
MessageEntity msg = new MessageEntity();
|
||||
msg.setMessageId(UUID.randomUUID());
|
||||
msg.setSenderId(virtualId);
|
||||
msg.setReceiverId(userId);
|
||||
msg.setText(req.text().trim());
|
||||
msg.setSentAt(now);
|
||||
messageRepo.save(msg);
|
||||
LOGGER.debug("Location {} hat Antwort an User {} gesendet", locationId, userId);
|
||||
|
||||
long unread = messageRepo.countUnread(userId);
|
||||
sseService.push(userId, "DM", Map.of("unreadCount", unread, "senderId", virtualId.toString()));
|
||||
return ResponseEntity.status(201).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ermittelt den Lock-Status für eine Konversation.
|
||||
* Gibt ein String-Array zurück: [canReply, lockedByMe, lockedByName|null]
|
||||
*/
|
||||
private String[] resolveLockStatus(UUID locationId, UUID visitorId, UUID myId) {
|
||||
var lockOpt = lockRepo.findByLocationIdAndVisitorId(locationId, visitorId);
|
||||
if (lockOpt.isEmpty()) return new String[]{"true", "false", null};
|
||||
LocationInboxLockEntity lock = lockOpt.get();
|
||||
boolean expired = lock.getLockedAt().isBefore(LocalDateTime.now().minusMinutes(LOCK_TIMEOUT_MINUTES));
|
||||
if (expired) return new String[]{"true", "false", null};
|
||||
if (lock.getLockedByUserId().equals(myId)) return new String[]{"true", "true", null};
|
||||
String lockerName = userRepo.findById(lock.getLockedByUserId())
|
||||
.map(u -> u.getName()).orElse("einem anderen Admin");
|
||||
return new String[]{"false", "false", lockerName};
|
||||
}
|
||||
|
||||
static double haversineKm(double lat1, double lon1, double lat2, double lon2) {
|
||||
|
||||
@@ -27,10 +27,16 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import de.oaa.xxx.location.entity.LocationEventAttendeeEntity;
|
||||
import de.oaa.xxx.location.entity.LocationEventEntity;
|
||||
import de.oaa.xxx.location.entity.LocationFollowEntity;
|
||||
import de.oaa.xxx.location.repository.LocationAdminRepository;
|
||||
import de.oaa.xxx.location.repository.LocationEventAttendeeRepository;
|
||||
import de.oaa.xxx.location.repository.LocationEventRepository;
|
||||
import de.oaa.xxx.location.repository.LocationFollowRepository;
|
||||
import de.oaa.xxx.location.repository.LocationRepository;
|
||||
import de.oaa.xxx.feed.entity.FeedPostEntity;
|
||||
import de.oaa.xxx.feed.repository.FeedPostRepository;
|
||||
import de.oaa.xxx.gruppe.BeitragTyp;
|
||||
import de.oaa.xxx.social.SystemMessageService;
|
||||
import de.oaa.xxx.social.entity.MessageCause;
|
||||
import de.oaa.xxx.user.UserEntity;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
import de.oaa.xxx.user.UserService;
|
||||
@@ -42,25 +48,41 @@ public class LocationEventController {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(LocationEventController.class);
|
||||
private static final int MAX_BATCH_SIZE = 50;
|
||||
|
||||
private final LocationRepository locationRepo;
|
||||
private final LocationEventRepository eventRepo;
|
||||
private final LocationRepository locationRepo;
|
||||
private final LocationEventRepository eventRepo;
|
||||
private final LocationEventAttendeeRepository attendeeRepo;
|
||||
private final LocationFollowRepository followRepo;
|
||||
private final UserRepository userRepo;
|
||||
private final UserService userService;
|
||||
private final LocationFollowRepository followRepo;
|
||||
private final LocationAdminRepository adminRepo;
|
||||
private final UserRepository userRepo;
|
||||
private final UserService userService;
|
||||
private final SystemMessageService systemMessageService;
|
||||
private final FeedPostRepository feedPostRepo;
|
||||
|
||||
public LocationEventController(LocationRepository locationRepo,
|
||||
LocationEventRepository eventRepo,
|
||||
LocationEventAttendeeRepository attendeeRepo,
|
||||
LocationFollowRepository followRepo,
|
||||
LocationAdminRepository adminRepo,
|
||||
UserRepository userRepo,
|
||||
UserService userService) {
|
||||
this.locationRepo = locationRepo;
|
||||
this.eventRepo = eventRepo;
|
||||
this.attendeeRepo = attendeeRepo;
|
||||
this.followRepo = followRepo;
|
||||
this.userRepo = userRepo;
|
||||
this.userService = userService;
|
||||
UserService userService,
|
||||
SystemMessageService systemMessageService,
|
||||
FeedPostRepository feedPostRepo) {
|
||||
this.locationRepo = locationRepo;
|
||||
this.eventRepo = eventRepo;
|
||||
this.attendeeRepo = attendeeRepo;
|
||||
this.followRepo = followRepo;
|
||||
this.adminRepo = adminRepo;
|
||||
this.userRepo = userRepo;
|
||||
this.userService = userService;
|
||||
this.systemMessageService = systemMessageService;
|
||||
this.feedPostRepo = feedPostRepo;
|
||||
}
|
||||
|
||||
private boolean isLocationAdmin(UUID locationId, UUID userId) {
|
||||
return locationRepo.findById(locationId)
|
||||
.map(l -> l.getOwnerId().equals(userId)
|
||||
|| adminRepo.existsByLocationIdAndUserId(locationId, userId))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
// ── DTOs ─────────────────────────────────────────────────────────────────
|
||||
@@ -91,6 +113,7 @@ public class LocationEventController {
|
||||
LocalDateTime startAt,
|
||||
LocalDateTime createdAt,
|
||||
boolean attendingMe,
|
||||
boolean isAdmin,
|
||||
List<AttendeeDto> attendees
|
||||
) {}
|
||||
|
||||
@@ -143,6 +166,25 @@ public class LocationEventController {
|
||||
event.setCreatedAt(LocalDateTime.now());
|
||||
eventRepo.save(event);
|
||||
|
||||
// Feed-Post automatisch anlegen
|
||||
try {
|
||||
String locationName = locOpt.get().getName();
|
||||
String dateStr = event.getStartAt().format(
|
||||
java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy 'um' HH:mm 'Uhr'"));
|
||||
FeedPostEntity feedPost = new FeedPostEntity();
|
||||
feedPost.setPostId(UUID.randomUUID());
|
||||
feedPost.setAuthorId(myId);
|
||||
feedPost.setText("📍 Neue Veranstaltung bei " + locationName + ": \"" + event.getTitle() + "\" - " + dateStr);
|
||||
feedPost.setBilder(event.getImageData() != null ? List.of(event.getImageData()) : List.of());
|
||||
feedPost.setBeitragTyp(BeitragTyp.TEXT);
|
||||
feedPost.setPublic(true);
|
||||
feedPost.setCreatedAt(LocalDateTime.now());
|
||||
feedPost.setTargetUrl("/community/event-detail.html?id=" + event.getEventId());
|
||||
feedPostRepo.save(feedPost);
|
||||
} catch (Exception ex) {
|
||||
LOGGER.warn("Feed-Post für Event {} konnte nicht angelegt werden: {}", event.getEventId(), ex.getMessage());
|
||||
}
|
||||
|
||||
LOGGER.info("Location {} hat Event {} angelegt", locationId, event.getEventId());
|
||||
return ResponseEntity.status(201).body(toDetail(event, locOpt.get().getName(), myId));
|
||||
}
|
||||
@@ -157,17 +199,23 @@ public class LocationEventController {
|
||||
UUID myId = userService.requireUser(principal).getUserId();
|
||||
var locOpt = locationRepo.findById(locationId);
|
||||
if (locOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
if (!locOpt.get().getOwnerId().equals(myId)) return ResponseEntity.status(403).build();
|
||||
if (!isLocationAdmin(locationId, myId)) return ResponseEntity.status(403).build();
|
||||
|
||||
var evtOpt = eventRepo.findById(eventId);
|
||||
if (evtOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
if (!evtOpt.get().getLocationId().equals(locationId)) return ResponseEntity.status(400).build();
|
||||
|
||||
// Veranstaltungen in der Vergangenheit dürfen nicht bearbeitet werden
|
||||
if (evtOpt.get().getStartAt().isBefore(LocalDateTime.now())) return ResponseEntity.status(422).build();
|
||||
|
||||
LocationEventEntity event = evtOpt.get();
|
||||
if (req.title() != null && !req.title().isBlank()) event.setTitle(req.title().trim());
|
||||
if (req.description() != null) event.setDescription(req.description().trim());
|
||||
if (req.imageData() != null) event.setImageData(req.imageData());
|
||||
if (req.startAt() != null) event.setStartAt(req.startAt());
|
||||
if (req.startAt() != null) {
|
||||
if (req.startAt().isBefore(LocalDateTime.now())) return ResponseEntity.status(422).build();
|
||||
event.setStartAt(req.startAt());
|
||||
}
|
||||
eventRepo.save(event);
|
||||
|
||||
return ResponseEntity.ok(toDetail(event, locOpt.get().getName(), myId));
|
||||
@@ -183,14 +231,27 @@ public class LocationEventController {
|
||||
UUID myId = userService.requireUser(principal).getUserId();
|
||||
var locOpt = locationRepo.findById(locationId);
|
||||
if (locOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
if (!locOpt.get().getOwnerId().equals(myId)) return ResponseEntity.status(403).build();
|
||||
if (!isLocationAdmin(locationId, myId)) return ResponseEntity.status(403).build();
|
||||
|
||||
var evtOpt = eventRepo.findById(eventId);
|
||||
if (evtOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
if (!evtOpt.get().getLocationId().equals(locationId)) return ResponseEntity.status(400).build();
|
||||
var evt = evtOpt.get();
|
||||
if (!evt.getLocationId().equals(locationId)) return ResponseEntity.status(400).build();
|
||||
|
||||
// Alle Teilnehmenden sammeln und benachrichtigen
|
||||
List<LocationEventAttendeeEntity> attendees = attendeeRepo.findByEventIdOrderByRegisteredAtAsc(eventId);
|
||||
String locationName = locOpt.get().getName();
|
||||
String notifyText = "Die Veranstaltung \"" + evt.getTitle() + "\" bei " + locationName + " wurde abgesagt.";
|
||||
String targetUrl = "/community/location-detail.html?id=" + locationId;
|
||||
|
||||
attendeeRepo.deleteByEventId(eventId);
|
||||
eventRepo.delete(evtOpt.get());
|
||||
eventRepo.delete(evt);
|
||||
|
||||
attendees.stream()
|
||||
.map(LocationEventAttendeeEntity::getUserId)
|
||||
.filter(uid -> !uid.equals(myId))
|
||||
.forEach(uid -> systemMessageService.send(myId, uid, notifyText, targetUrl, MessageCause.EVENT_CANCELLED));
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@@ -236,6 +297,78 @@ public class LocationEventController {
|
||||
return ResponseEntity.ok(Map.of("attending", attending, "attendeeCount", count));
|
||||
}
|
||||
|
||||
// ── Meine angemeldeten Events (für Home) ─────────────────────────────────
|
||||
|
||||
@GetMapping("/location-events/attending-next")
|
||||
public ResponseEntity<List<EventPreviewDto>> getAttendingNext(Principal principal) {
|
||||
UUID myId = userService.requireUser(principal).getUserId();
|
||||
|
||||
List<UUID> myEventIds = attendeeRepo.findByUserId(myId).stream()
|
||||
.map(LocationEventAttendeeEntity::getEventId)
|
||||
.toList();
|
||||
|
||||
if (myEventIds.isEmpty()) return ResponseEntity.ok(List.of());
|
||||
|
||||
Map<UUID, de.oaa.xxx.location.entity.LocationEntity> locationById = new java.util.HashMap<>();
|
||||
|
||||
List<EventPreviewDto> result = eventRepo
|
||||
.findUpcomingByEventIds(myEventIds, LocalDateTime.now())
|
||||
.stream()
|
||||
.map(e -> {
|
||||
var loc = locationById.computeIfAbsent(e.getLocationId(),
|
||||
id -> locationRepo.findById(id).orElse(null));
|
||||
String locName = loc != null ? loc.getName() : "";
|
||||
return toPreview(e, locName, 0, 0, myId);
|
||||
})
|
||||
.toList();
|
||||
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
// ── Nächste Events je abonnierter Location (für Home) ────────────────────
|
||||
|
||||
@GetMapping("/location-events/followed-next")
|
||||
public ResponseEntity<List<EventPreviewDto>> getFollowedNext(Principal principal) {
|
||||
UUID myId = userService.requireUser(principal).getUserId();
|
||||
|
||||
List<UUID> followedIds = followRepo.findByUserId(myId).stream()
|
||||
.map(LocationFollowEntity::getLocationId)
|
||||
.toList();
|
||||
|
||||
if (followedIds.isEmpty()) return ResponseEntity.ok(List.of());
|
||||
|
||||
// Events ausschließen, bei denen der User bereits angemeldet ist
|
||||
Set<UUID> attendingEventIds = attendeeRepo.findByUserId(myId).stream()
|
||||
.map(LocationEventAttendeeEntity::getEventId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Map<UUID, de.oaa.xxx.location.entity.LocationEntity> locationById =
|
||||
locationRepo.findAllById(followedIds).stream()
|
||||
.collect(Collectors.toMap(
|
||||
de.oaa.xxx.location.entity.LocationEntity::getLocationId,
|
||||
l -> l));
|
||||
|
||||
// Ein Event pro Location: je das nächste, das noch nicht begonnen hat und nicht attending
|
||||
List<EventPreviewDto> result = eventRepo
|
||||
.findUpcomingByLocationIds(followedIds, LocalDateTime.now())
|
||||
.stream()
|
||||
.filter(e -> !attendingEventIds.contains(e.getEventId()))
|
||||
.collect(Collectors.toMap(
|
||||
LocationEventEntity::getLocationId,
|
||||
e -> e,
|
||||
(existing, replacement) -> existing))
|
||||
.values().stream()
|
||||
.sorted(Comparator.comparing(LocationEventEntity::getStartAt))
|
||||
.map(e -> {
|
||||
var loc = locationById.get(e.getLocationId());
|
||||
String locName = loc != null ? loc.getName() : "";
|
||||
return toPreview(e, locName, 0, 0, myId);
|
||||
})
|
||||
.toList();
|
||||
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
// ── Event-Suche (IDs + Batch) ─────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -257,7 +390,7 @@ public class LocationEventController {
|
||||
LocalDateTime fromDt = from != null ? LocalDateTime.parse(from) : LocalDateTime.now();
|
||||
LocalDateTime toDt = to != null ? LocalDateTime.parse(to) : fromDt.plusMonths(3);
|
||||
|
||||
// Abonnierte Locations – deren Events werden immer eingeschlossen
|
||||
// Abonnierte Locations - deren Events werden immer eingeschlossen
|
||||
Set<UUID> followedLocationIds = followRepo.findByUserId(myId).stream()
|
||||
.map(LocationFollowEntity::getLocationId)
|
||||
.collect(Collectors.toSet());
|
||||
@@ -374,11 +507,12 @@ public class LocationEventController {
|
||||
.toList();
|
||||
|
||||
boolean attendingMe = attendeeRepo.findByEventIdAndUserId(e.getEventId(), myId).isPresent();
|
||||
boolean isAdmin = isLocationAdmin(e.getLocationId(), myId);
|
||||
|
||||
return new EventDetailDto(
|
||||
e.getEventId(), e.getLocationId(), locationName,
|
||||
e.getTitle(), e.getDescription(), e.getImageData(),
|
||||
e.getStartAt(), e.getCreatedAt(),
|
||||
attendingMe, attendees);
|
||||
attendingMe, isAdmin, attendees);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package de.oaa.xxx.location.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.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "location_admin")
|
||||
public class LocationAdminEntity {
|
||||
|
||||
@Id
|
||||
@Column
|
||||
private UUID adminId;
|
||||
|
||||
@Column(nullable = false)
|
||||
private UUID locationId;
|
||||
|
||||
@Column(nullable = false)
|
||||
private UUID userId;
|
||||
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime addedAt;
|
||||
}
|
||||
@@ -52,4 +52,8 @@ public class LocationEntity {
|
||||
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/** Virtuelle Benutzer-ID für das Nachrichtensystem (einmalig generiert, unveränderlich) */
|
||||
@Column(unique = true)
|
||||
private UUID virtualUserId;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package de.oaa.xxx.location.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "location_inbox_lock",
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"location_id", "visitor_id"}))
|
||||
public class LocationInboxLockEntity {
|
||||
|
||||
@Id
|
||||
@Column
|
||||
private UUID lockId;
|
||||
|
||||
@Column(name = "location_id", nullable = false)
|
||||
private UUID locationId;
|
||||
|
||||
/** Die kontaktierende Person */
|
||||
@Column(name = "visitor_id", nullable = false)
|
||||
private UUID visitorId;
|
||||
|
||||
/** Der Admin/Inhaber, der gerade antwortet */
|
||||
@Column(name = "locked_by_user_id", nullable = false)
|
||||
private UUID lockedByUserId;
|
||||
|
||||
/** Letzte Aktivität des Lock-Inhabers (wird bei jeder Antwort erneuert) */
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime lockedAt;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.oaa.xxx.location.repository;
|
||||
|
||||
import de.oaa.xxx.location.entity.LocationAdminEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface LocationAdminRepository extends JpaRepository<LocationAdminEntity, UUID> {
|
||||
|
||||
List<LocationAdminEntity> findByLocationId(UUID locationId);
|
||||
Optional<LocationAdminEntity> findByLocationIdAndUserId(UUID locationId, UUID userId);
|
||||
boolean existsByLocationIdAndUserId(UUID locationId, UUID userId);
|
||||
void deleteByLocationId(UUID locationId);
|
||||
void deleteByLocationIdAndUserId(UUID locationId, UUID userId);
|
||||
}
|
||||
@@ -11,6 +11,8 @@ public interface LocationEventAttendeeRepository extends JpaRepository<LocationE
|
||||
|
||||
List<LocationEventAttendeeEntity> findByEventIdOrderByRegisteredAtAsc(UUID eventId);
|
||||
|
||||
List<LocationEventAttendeeEntity> findByUserId(UUID userId);
|
||||
|
||||
Optional<LocationEventAttendeeEntity> findByEventIdAndUserId(UUID eventId, UUID userId);
|
||||
|
||||
long countByEventId(UUID eventId);
|
||||
|
||||
@@ -3,8 +3,10 @@ package de.oaa.xxx.location.repository;
|
||||
import de.oaa.xxx.location.entity.LocationEventEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -12,6 +14,12 @@ public interface LocationEventRepository extends JpaRepository<LocationEventEnti
|
||||
|
||||
List<LocationEventEntity> findByLocationIdOrderByStartAtAsc(UUID locationId);
|
||||
|
||||
@Query("SELECT e FROM LocationEventEntity e WHERE e.locationId IN :locationIds AND e.startAt >= :from ORDER BY e.startAt ASC")
|
||||
List<LocationEventEntity> findUpcomingByLocationIds(@Param("locationIds") Collection<UUID> locationIds, @Param("from") LocalDateTime from);
|
||||
|
||||
@Query("SELECT e FROM LocationEventEntity e WHERE e.eventId IN :eventIds AND e.startAt >= :from ORDER BY e.startAt ASC")
|
||||
List<LocationEventEntity> findUpcomingByEventIds(@Param("eventIds") Collection<UUID> eventIds, @Param("from") LocalDateTime from);
|
||||
|
||||
/** Alle zukünftigen Events mit Koordinaten ihrer Location (für Umkreis-Suche) */
|
||||
@Query("""
|
||||
SELECT e FROM LocationEventEntity e
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.oaa.xxx.location.repository;
|
||||
|
||||
import de.oaa.xxx.location.entity.LocationInboxLockEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface LocationInboxLockRepository extends JpaRepository<LocationInboxLockEntity, UUID> {
|
||||
|
||||
Optional<LocationInboxLockEntity> findByLocationIdAndVisitorId(UUID locationId, UUID visitorId);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import de.oaa.xxx.location.entity.LocationEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface LocationRepository extends JpaRepository<LocationEntity, UUID> {
|
||||
@@ -12,4 +13,8 @@ public interface LocationRepository extends JpaRepository<LocationEntity, UUID>
|
||||
|
||||
/** Alle Locations mit gesetzten Koordinaten (für Umkreissuche) */
|
||||
List<LocationEntity> findByLatIsNotNullAndLonIsNotNull();
|
||||
|
||||
Optional<LocationEntity> findByVirtualUserId(UUID virtualUserId);
|
||||
|
||||
List<LocationEntity> findByVirtualUserIdIsNotNull();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user