Verschiebung nach anderem RePo - nun pro Projekt getrennt

This commit is contained in:
2026-04-01 10:41:19 +02:00
commit 7b9eda1d62
1048 changed files with 93351 additions and 0 deletions

View File

@@ -0,0 +1,512 @@
package de.oaa.xxx.gruppe;
import de.oaa.xxx.gruppe.dto.*;
import de.oaa.xxx.gruppe.entity.*;
import de.oaa.xxx.gruppe.repository.*;
import de.oaa.xxx.social.entity.KommentarEntity;
import de.oaa.xxx.social.repository.KommentarRepository;
import de.oaa.xxx.user.UserEntity;
import de.oaa.xxx.user.UserRepository;
import de.oaa.xxx.user.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
import java.time.LocalDateTime;
import java.util.*;
@RestController
@RequestMapping("/gruppen")
public class GruppeController {
private static final Logger LOGGER = LoggerFactory.getLogger(GruppeController.class);
private final GruppeRepository gruppeRepository;
private final GruppenmitgliedRepository mitgliedRepository;
private final BeitrittsanfrageRepository anfrageRepository;
private final GruppenbeitragRepository beitragRepository;
private final UmfrageOptionRepository optionRepository;
private final UmfrageStimmeRepository stimmeRepository;
private final GruppenbeitragLikeRepository likeRepository;
private final BeitragMeldungRepository meldungRepository;
private final KommentarRepository kommentarRepository;
private final UserRepository userRepository;
private final UserService userService;
public GruppeController(GruppeRepository gruppeRepository,
GruppenmitgliedRepository mitgliedRepository,
BeitrittsanfrageRepository anfrageRepository,
GruppenbeitragRepository beitragRepository,
UmfrageOptionRepository optionRepository,
UmfrageStimmeRepository stimmeRepository,
GruppenbeitragLikeRepository likeRepository,
BeitragMeldungRepository meldungRepository,
KommentarRepository kommentarRepository,
UserRepository userRepository,
UserService userService) {
this.gruppeRepository = gruppeRepository;
this.mitgliedRepository = mitgliedRepository;
this.anfrageRepository = anfrageRepository;
this.beitragRepository = beitragRepository;
this.optionRepository = optionRepository;
this.stimmeRepository = stimmeRepository;
this.likeRepository = likeRepository;
this.meldungRepository = meldungRepository;
this.kommentarRepository = kommentarRepository;
this.userRepository = userRepository;
this.userService = userService;
}
record CreateGruppeRequest(String name, String beschreibung, String bild, boolean isPrivate) {}
record JoinRequest(String nachricht) {}
record UpdateGruppeRequest(String name, String beschreibung, String bild, Boolean isPrivate) {}
// ── GET /gruppe/search?q= ──
@GetMapping("/search")
public ResponseEntity<List<GruppeDto>> search(@RequestParam String q, Principal principal) {
UUID myId = resolveMyId(principal);
if (myId == null) return ResponseEntity.status(401).build();
List<GruppeEntity> gruppen = gruppeRepository.findByNameContainingIgnoreCase(q)
.stream().limit(30).toList();
List<UUID> gruppeIds = gruppen.stream().map(GruppeEntity::getGruppeId).toList();
Map<UUID, LocalDateTime> latestActivity = buildLatestActivityMap(gruppeIds);
List<GruppeDto> result = gruppen.stream()
.map(g -> toDto(g, myId))
.sorted((a, b) -> {
LocalDateTime la = latestActivity.getOrDefault(a.gruppeId(), a.createdAt());
LocalDateTime lb = latestActivity.getOrDefault(b.gruppeId(), b.createdAt());
return lb.compareTo(la);
})
.toList();
return ResponseEntity.ok(result);
}
// ── GET /gruppe/mine ──
@GetMapping("/mine")
public ResponseEntity<List<GruppeDto>> mine(Principal principal) {
UUID myId = resolveMyId(principal);
if (myId == null) return ResponseEntity.status(401).build();
List<GruppeEntity> gruppen = mitgliedRepository.findByUserId(myId)
.stream()
.map(m -> gruppeRepository.findById(m.getGruppeId()).orElse(null))
.filter(Objects::nonNull)
.toList();
List<UUID> gruppeIds = gruppen.stream().map(GruppeEntity::getGruppeId).toList();
Map<UUID, LocalDateTime> latestActivity = buildLatestActivityMap(gruppeIds);
List<GruppeDto> result = gruppen.stream()
.map(g -> toDto(g, myId))
.sorted((a, b) -> {
LocalDateTime la = latestActivity.getOrDefault(a.gruppeId(), a.createdAt());
LocalDateTime lb = latestActivity.getOrDefault(b.gruppeId(), b.createdAt());
return lb.compareTo(la);
})
.toList();
return ResponseEntity.ok(result);
}
// ── GET /gruppe/{id} ──
@GetMapping("/{id}")
public ResponseEntity<GruppeDto> getGruppe(@PathVariable UUID id, Principal principal) {
UUID myId = resolveMyId(principal);
if (myId == null) return ResponseEntity.status(401).build();
return gruppeRepository.findById(id)
.map(g -> ResponseEntity.ok(toDto(g, myId)))
.orElse(ResponseEntity.notFound().build());
}
// ── POST /gruppe ──
@PostMapping
public ResponseEntity<GruppeDto> createGruppe(@RequestBody CreateGruppeRequest req, Principal principal) {
UUID myId = resolveMyId(principal);
if (myId == null) return ResponseEntity.status(401).build();
if (req.name() == null || req.name().isBlank()) return ResponseEntity.badRequest().build();
GruppeEntity gruppe = new GruppeEntity();
gruppe.setGruppeId(UUID.randomUUID());
gruppe.setName(req.name().trim());
gruppe.setBeschreibung(req.beschreibung());
gruppe.setBild(req.bild());
gruppe.setPrivate(req.isPrivate());
gruppe.setCreatedAt(LocalDateTime.now());
gruppe.setCreatedByUserId(myId);
gruppeRepository.save(gruppe);
GruppenmitgliedEntity admin = new GruppenmitgliedEntity();
admin.setMitgliedId(UUID.randomUUID());
admin.setGruppeId(gruppe.getGruppeId());
admin.setUserId(myId);
admin.setRolle(GruppenRolle.ADMIN);
admin.setJoinedAt(LocalDateTime.now());
mitgliedRepository.save(admin);
LOGGER.info("User {} hat Gruppe '{}' ({}) erstellt", myId, gruppe.getName(), gruppe.getGruppeId());
return ResponseEntity.status(201).body(toDto(gruppe, myId));
}
// ── PUT /gruppe/{id} ──
@PutMapping("/{id}")
public ResponseEntity<GruppeDto> updateGruppe(@PathVariable UUID id,
@RequestBody UpdateGruppeRequest req,
Principal principal) {
UUID myId = resolveMyId(principal);
if (myId == null) return ResponseEntity.status(401).build();
var gruppeOpt = gruppeRepository.findById(id);
if (gruppeOpt.isEmpty()) return ResponseEntity.notFound().build();
GruppeEntity gruppe = gruppeOpt.get();
if (!isAdmin(id, myId)) return ResponseEntity.status(403).build();
if (req.name() != null && !req.name().isBlank()) gruppe.setName(req.name().trim());
if (req.beschreibung() != null) gruppe.setBeschreibung(req.beschreibung());
if (req.bild() != null) gruppe.setBild(req.bild());
if (req.isPrivate() != null) gruppe.setPrivate(req.isPrivate());
gruppeRepository.save(gruppe);
LOGGER.debug("User {} hat Gruppe {} aktualisiert", myId, id);
return ResponseEntity.ok(toDto(gruppe, myId));
}
// ── DELETE /gruppe/{id} ──
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteGruppe(@PathVariable UUID id, Principal principal) {
UUID myId = resolveMyId(principal);
if (myId == null) return ResponseEntity.status(401).build();
var gruppeOpt = gruppeRepository.findById(id);
if (gruppeOpt.isEmpty()) return ResponseEntity.notFound().build();
if (!isAdmin(id, myId)) return ResponseEntity.status(403).build();
// Cascade delete
List<GruppenbeitragEntity> beitraege = beitragRepository.findByGruppeIdOrderByCreatedAtDesc(id);
for (GruppenbeitragEntity b : beitraege) {
UUID bid = b.getBeitragId();
meldungRepository.deleteByBeitragId(bid);
stimmeRepository.deleteByBeitragId(bid);
optionRepository.deleteByBeitragId(bid);
likeRepository.deleteByBeitragId(bid);
// Kommentare on GROUP_POST
List<KommentarEntity> kommentare = kommentarRepository
.findByTargetTypeAndTargetIdOrderByCreatedAtAsc("GROUP_POST", bid);
for (KommentarEntity k : kommentare) {
kommentarRepository.delete(k);
}
}
beitragRepository.deleteByGruppeId(id);
anfrageRepository.deleteByGruppeId(id);
mitgliedRepository.deleteByGruppeId(id);
gruppeRepository.deleteById(id);
LOGGER.info("User {} hat Gruppe {} gelöscht", myId, id);
return ResponseEntity.noContent().build();
}
// ── POST /gruppe/{id}/join ──
@PostMapping("/{id}/join")
public ResponseEntity<Void> join(@PathVariable UUID id,
@RequestBody(required = false) JoinRequest req,
Principal principal) {
UUID myId = resolveMyId(principal);
if (myId == null) return ResponseEntity.status(401).build();
var gruppeOpt = gruppeRepository.findById(id);
if (gruppeOpt.isEmpty()) return ResponseEntity.notFound().build();
GruppeEntity gruppe = gruppeOpt.get();
if (mitgliedRepository.findFirstByGruppeIdAndUserId(id, myId).isPresent())
return ResponseEntity.status(409).build();
if (gruppe.isPrivate()) {
// Check no pending request already
var existingReq = anfrageRepository.findByGruppeIdAndUserId(id, myId);
if (existingReq.isPresent() && existingReq.get().getStatus() == AnfrageStatus.AUSSTEHEND)
return ResponseEntity.status(409).build();
BeitrittsanfrageEntity anfrage = new BeitrittsanfrageEntity();
anfrage.setAnfrageId(UUID.randomUUID());
anfrage.setGruppeId(id);
anfrage.setUserId(myId);
anfrage.setNachricht(req != null ? req.nachricht() : null);
anfrage.setAngefragtAt(LocalDateTime.now());
anfrage.setStatus(AnfrageStatus.AUSSTEHEND);
anfrageRepository.save(anfrage);
LOGGER.info("User {} hat Beitrittsanfrage für private Gruppe {} gestellt", myId, id);
return ResponseEntity.status(201).build();
} else {
GruppenmitgliedEntity mitglied = new GruppenmitgliedEntity();
mitglied.setMitgliedId(UUID.randomUUID());
mitglied.setGruppeId(id);
mitglied.setUserId(myId);
mitglied.setRolle(GruppenRolle.MITGLIED);
mitglied.setJoinedAt(LocalDateTime.now());
mitgliedRepository.save(mitglied);
LOGGER.info("User {} ist Gruppe {} beigetreten", myId, id);
return ResponseEntity.status(201).build();
}
}
// ── DELETE /gruppe/{id}/leave ──
@DeleteMapping("/{id}/leave")
public ResponseEntity<Void> leave(@PathVariable UUID id, Principal principal) {
UUID myId = resolveMyId(principal);
if (myId == null) return ResponseEntity.status(401).build();
mitgliedRepository.deleteByGruppeIdAndUserId(id, myId);
LOGGER.info("User {} hat Gruppe {} verlassen", myId, id);
return ResponseEntity.noContent().build();
}
// ── GET /gruppe/{id}/members ──
@GetMapping("/{id}/members")
public ResponseEntity<List<Map<String, Object>>> getMembers(@PathVariable UUID id, Principal principal) {
UUID myId = resolveMyId(principal);
if (myId == null) return ResponseEntity.status(401).build();
if (mitgliedRepository.findFirstByGruppeIdAndUserId(id, myId).isEmpty())
return ResponseEntity.status(403).build();
List<Map<String, Object>> result = mitgliedRepository.findByGruppeId(id).stream()
.map(m -> {
Map<String, Object> map = new LinkedHashMap<>();
map.put("mitgliedId", m.getMitgliedId());
map.put("userId", m.getUserId());
map.put("rolle", m.getRolle().name());
map.put("joinedAt", m.getJoinedAt());
userRepository.findById(m.getUserId()).ifPresent(u -> {
map.put("userName", u.getName());
map.put("userPicture", u.getProfilePicture());
});
return map;
})
.toList();
return ResponseEntity.ok(result);
}
// ── DELETE /gruppe/{id}/members/{userId} ──
@DeleteMapping("/{id}/members/{userId}")
public ResponseEntity<Void> removeMember(@PathVariable UUID id,
@PathVariable UUID userId,
Principal principal) {
UUID myId = resolveMyId(principal);
if (myId == null) return ResponseEntity.status(401).build();
if (!isAdmin(id, myId)) return ResponseEntity.status(403).build();
mitgliedRepository.deleteByGruppeIdAndUserId(id, userId);
LOGGER.warn("Admin {} hat User {} aus Gruppe {} entfernt", myId, userId, id);
return ResponseEntity.noContent().build();
}
// ── POST /gruppe/{id}/members/{userId}/promote ──
@PostMapping("/{id}/members/{userId}/promote")
public ResponseEntity<Void> promote(@PathVariable UUID id,
@PathVariable UUID userId,
Principal principal) {
UUID myId = resolveMyId(principal);
if (myId == null) return ResponseEntity.status(401).build();
if (!isAdmin(id, myId)) return ResponseEntity.status(403).build();
var m = mitgliedRepository.findFirstByGruppeIdAndUserId(id, userId);
if (m.isEmpty()) return ResponseEntity.notFound().build();
m.get().setRolle(GruppenRolle.ADMIN);
mitgliedRepository.save(m.get());
LOGGER.info("Admin {} hat User {} in Gruppe {} zum Admin befördert", myId, userId, id);
return ResponseEntity.ok().build();
}
// ── GET /gruppe/{id}/requests ──
@GetMapping("/{id}/requests")
public ResponseEntity<List<BeitrittsanfrageDto>> getRequests(@PathVariable UUID id, Principal principal) {
UUID myId = resolveMyId(principal);
if (myId == null) return ResponseEntity.status(401).build();
if (!isAdmin(id, myId)) return ResponseEntity.status(403).build();
List<BeitrittsanfrageDto> dtos = anfrageRepository
.findByGruppeIdAndStatus(id, AnfrageStatus.AUSSTEHEND)
.stream()
.map(a -> {
UserEntity u = userRepository.findById(a.getUserId()).orElse(null);
return new BeitrittsanfrageDto(a.getAnfrageId(), a.getGruppeId(), a.getUserId(),
u != null ? u.getName() : "Unbekannt",
u != null ? u.getProfilePicture() : null,
a.getNachricht(), a.getAngefragtAt());
})
.toList();
return ResponseEntity.ok(dtos);
}
// ── POST /gruppe/{id}/requests/{reqId}/approve ──
@PostMapping("/{id}/requests/{reqId}/approve")
public ResponseEntity<Void> approveRequest(@PathVariable UUID id,
@PathVariable UUID reqId,
Principal principal) {
UUID myId = resolveMyId(principal);
if (myId == null) return ResponseEntity.status(401).build();
if (!isAdmin(id, myId)) return ResponseEntity.status(403).build();
var anfOpt = anfrageRepository.findById(reqId);
if (anfOpt.isEmpty()) return ResponseEntity.notFound().build();
BeitrittsanfrageEntity anfrage = anfOpt.get();
anfrage.setStatus(AnfrageStatus.GENEHMIGT);
anfrageRepository.save(anfrage);
LOGGER.info("Admin {} hat Beitrittsanfrage {} (User: {}) für Gruppe {} genehmigt", myId, reqId, anfrage.getUserId(), id);
if (mitgliedRepository.findFirstByGruppeIdAndUserId(id, anfrage.getUserId()).isEmpty()) {
GruppenmitgliedEntity mitglied = new GruppenmitgliedEntity();
mitglied.setMitgliedId(UUID.randomUUID());
mitglied.setGruppeId(id);
mitglied.setUserId(anfrage.getUserId());
mitglied.setRolle(GruppenRolle.MITGLIED);
mitglied.setJoinedAt(LocalDateTime.now());
mitgliedRepository.save(mitglied);
}
return ResponseEntity.ok().build();
}
// ── DELETE /gruppe/{id}/requests/{reqId} ──
@DeleteMapping("/{id}/requests/{reqId}")
public ResponseEntity<Void> rejectRequest(@PathVariable UUID id,
@PathVariable UUID reqId,
Principal principal) {
UUID myId = resolveMyId(principal);
if (myId == null) return ResponseEntity.status(401).build();
if (!isAdmin(id, myId)) return ResponseEntity.status(403).build();
var anfOpt = anfrageRepository.findById(reqId);
if (anfOpt.isEmpty()) return ResponseEntity.notFound().build();
BeitrittsanfrageEntity anfrage = anfOpt.get();
anfrage.setStatus(AnfrageStatus.ABGELEHNT);
anfrageRepository.save(anfrage);
LOGGER.debug("Admin {} hat Beitrittsanfrage {} für Gruppe {} abgelehnt", myId, reqId, id);
return ResponseEntity.noContent().build();
}
// ── GET /gruppen/reports/pending/count ──
@GetMapping("/reports/pending/count")
public ResponseEntity<Long> pendingReportsCount(Principal principal) {
UUID myId = resolveMyId(principal);
if (myId == null) return ResponseEntity.status(401).build();
long count = mitgliedRepository.findByUserId(myId).stream()
.filter(m -> m.getRolle() == GruppenRolle.ADMIN)
.mapToLong(m -> {
List<GruppenbeitragEntity> beitraege = beitragRepository.findByGruppeIdOrderByCreatedAtDesc(m.getGruppeId());
List<UUID> ids = beitraege.stream().map(GruppenbeitragEntity::getBeitragId).toList();
return ids.isEmpty() ? 0L : meldungRepository.findByBeitragIdIn(ids).size();
})
.sum();
return ResponseEntity.ok(count);
}
// ── GET /gruppen/requests/pending/count ──
@GetMapping("/requests/pending/count")
public ResponseEntity<Long> pendingRequestCount(Principal principal) {
UUID myId = resolveMyId(principal);
if (myId == null) return ResponseEntity.status(401).build();
long count = mitgliedRepository.findByUserId(myId).stream()
.filter(m -> m.getRolle() == GruppenRolle.ADMIN)
.mapToLong(m -> anfrageRepository.findByGruppeIdAndStatus(m.getGruppeId(), AnfrageStatus.AUSSTEHEND).size())
.sum();
return ResponseEntity.ok(count);
}
// ── GET /gruppe/requests/mine ──
@GetMapping("/requests/mine")
public ResponseEntity<List<Map<String, Object>>> myRequests(Principal principal) {
UUID myId = resolveMyId(principal);
if (myId == null) return ResponseEntity.status(401).build();
List<Map<String, Object>> result = anfrageRepository.findByUserIdAndStatus(myId, AnfrageStatus.AUSSTEHEND)
.stream()
.map(a -> {
Map<String, Object> map = new LinkedHashMap<>();
map.put("anfrageId", a.getAnfrageId());
map.put("gruppeId", a.getGruppeId());
map.put("nachricht", a.getNachricht());
map.put("angefragtAt", a.getAngefragtAt());
gruppeRepository.findById(a.getGruppeId()).ifPresent(g -> map.put("gruppeName", g.getName()));
return map;
})
.toList();
return ResponseEntity.ok(result);
}
// ── DELETE /gruppe/{id}/requests/mine (withdraw own request) ──
@DeleteMapping("/{id}/requests/mine")
public ResponseEntity<Void> withdrawRequest(@PathVariable UUID id, Principal principal) {
UUID myId = resolveMyId(principal);
if (myId == null) return ResponseEntity.status(401).build();
anfrageRepository.findByGruppeIdAndUserId(id, myId).ifPresent(a -> {
if (a.getStatus() == AnfrageStatus.AUSSTEHEND) {
anfrageRepository.delete(a);
LOGGER.debug("User {} hat eigene Beitrittsanfrage für Gruppe {} zurückgezogen", myId, id);
}
});
return ResponseEntity.noContent().build();
}
// ── Helpers ──
private Map<UUID, LocalDateTime> buildLatestActivityMap(List<UUID> gruppeIds) {
if (gruppeIds.isEmpty()) return Map.of();
Map<UUID, LocalDateTime> map = new HashMap<>();
beitragRepository.findLatestCreatedAtByGruppeIds(gruppeIds).forEach(row -> {
UUID gId = (UUID) row[0];
LocalDateTime latest = (LocalDateTime) row[1];
map.put(gId, latest);
});
return map;
}
private UUID resolveMyId(Principal principal) {
if (principal == null) return null;
return userService.requireUser(principal).getUserId();
}
private boolean isAdmin(UUID gruppeId, UUID userId) {
return mitgliedRepository.findFirstByGruppeIdAndUserId(gruppeId, userId)
.map(m -> m.getRolle() == GruppenRolle.ADMIN)
.orElse(false);
}
GruppeDto toDto(GruppeEntity g, UUID myId) {
long memberCount = mitgliedRepository.countByGruppeId(g.getGruppeId());
long postCount = beitragRepository.findByGruppeIdOrderByCreatedAtDesc(g.getGruppeId()).size();
String myRole = mitgliedRepository.findFirstByGruppeIdAndUserId(g.getGruppeId(), myId)
.map(m -> m.getRolle().name())
.orElse(null);
String myRequestStatus = null;
if (myRole == null) {
myRequestStatus = anfrageRepository.findByGruppeIdAndUserId(g.getGruppeId(), myId)
.filter(a -> a.getStatus() == AnfrageStatus.AUSSTEHEND)
.map(a -> a.getStatus().name())
.orElse(null);
}
return new GruppeDto(g.getGruppeId(), g.getName(), g.getBeschreibung(), g.getBild(),
g.isPrivate(), g.getCreatedAt(), memberCount, postCount, myRole, myRequestStatus);
}
}