ModerationController.java
package com.wavii.controller;
import com.wavii.model.BandListing;
import com.wavii.model.User;
import com.wavii.repository.BandListingRepository;
import com.wavii.repository.UserRepository;
import com.wavii.service.NotificationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
@RestController
@RequestMapping("/api/moderation")
@RequiredArgsConstructor
@Slf4j
public class ModerationController {
private final BandListingRepository bandListingRepository;
private final UserRepository userRepository;
private final NotificationService notificationService;
@Value("${odoo.webhook-secret:}")
private String odooWebhookSecret;
@PostMapping("/odoo-webhook")
public ResponseEntity<?> odooWebhook(
@RequestHeader(value = "X-Odoo-Secret", required = false) String receivedSecret,
@RequestBody Map<String, String> body
) {
if (odooWebhookSecret == null || odooWebhookSecret.isBlank()
|| !odooWebhookSecret.equals(receivedSecret)) {
log.warn("Odoo moderation webhook: secreto invalido o ausente");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("message", "Secreto no valido"));
}
String action = body.get("action");
String reportType = body.get("reportType");
String targetReference = body.get("targetReference");
String reporterEmail = body.get("reporterEmail");
if (action == null || reportType == null || targetReference == null) {
return ResponseEntity.badRequest().body(Map.of("message", "Se requieren action, reportType y targetReference"));
}
if (!"band_listing".equals(reportType)) {
notifyReporter(reporterEmail, action, null);
return ResponseEntity.ok(Map.of("message", "Reporte actualizado"));
}
UUID listingId = parseBandListingReference(targetReference);
if (listingId == null) {
return ResponseEntity.badRequest().body(Map.of("message", "Referencia de anuncio invalida"));
}
BandListing listing = bandListingRepository.findById(listingId).orElse(null);
if (listing == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of("message", "Anuncio no encontrado"));
}
String normalizedAction = action.toLowerCase(Locale.ROOT);
if ("approve".equals(normalizedAction)) {
listing.setRemovedByModeration(true);
listing.setRemovedAt(LocalDateTime.now());
bandListingRepository.save(listing);
notifyListingCreator(listing);
notifyReporter(reporterEmail, "approve", listing);
return ResponseEntity.ok(Map.of("message", "Reporte aprobado y anuncio eliminado"));
}
if ("reject".equals(normalizedAction)) {
notifyReporter(reporterEmail, "reject", listing);
return ResponseEntity.ok(Map.of("message", "Reporte rechazado"));
}
return ResponseEntity.badRequest().body(Map.of("message", "Accion no valida. Usa approve o reject"));
}
private UUID parseBandListingReference(String targetReference) {
if (!targetReference.startsWith("band_listing:")) {
return null;
}
try {
return UUID.fromString(targetReference.substring("band_listing:".length()));
} catch (IllegalArgumentException e) {
return null;
}
}
private void notifyListingCreator(BandListing listing) {
notificationService.create(
listing.getCreator(),
"moderation_listing_removed",
"Anuncio eliminado",
"Tu anuncio \"" + listing.getTitle() + "\" ha sido eliminado tras revisar un reporte.",
Map.of("listingId", listing.getId().toString())
);
}
private void notifyReporter(String reporterEmail, String action, BandListing listing) {
if (reporterEmail == null || reporterEmail.isBlank()) {
return;
}
Optional<User> reporter = userRepository.findByEmail(reporterEmail);
if (reporter.isEmpty()) {
return;
}
boolean approved = "approve".equalsIgnoreCase(action);
String title = approved ? "Reporte aprobado" : "Reporte rechazado";
String body = approved
? "Hemos revisado tu reporte y se ha tomado accion."
: "Hemos revisado tu reporte y no se ha tomado accion sobre el contenido.";
Map<String, Object> data = listing != null
? Map.of("listingId", listing.getId().toString())
: Map.of();
notificationService.create(reporter.get(), approved ? "moderation_report_approved" : "moderation_report_rejected", title, body, data);
}
}