WinController.java
package com.archiweb.api;
import com.archiweb.model.User;
import com.archiweb.model.Win;
import com.archiweb.repository.UserRepository;
import com.archiweb.repository.WinRepository;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@RestController
@RequestMapping("/api/wins")
@Tag(
name = "Gains",
description = """
Module de gestion des **gains et lots remportés** par les participants.
Ce contrôleur permet :
- La **création** manuelle de gains pour les tests ou corrections administratives.
- La **consultation paginée** de tous les gains enregistrés.
- La **consultation individuelle** des gains d’un utilisateur.
- La **validation** de la remise d’un lot (marquage comme “remis”).
Accessible uniquement aux administrateurs et employés autorisés.
"""
)
public class WinController {
private final WinRepository winRepository;
private final UserRepository userRepository;
public WinController(WinRepository winRepository, UserRepository userRepository) {
this.winRepository = winRepository;
this.userRepository = userRepository;
}
// =========================================================
// 1️⃣ Liste paginée des gains
// =========================================================
@Operation(
summary = "Lister les gains (paginé)",
description = """
Renvoie une **liste paginée** de tous les gains enregistrés dans le système.
Ce point d’accès est principalement destiné aux administrateurs pour la supervision et le reporting.
Paramètres :
- `page` : numéro de page (défaut : 0)
- `size` : taille de page (défaut : 10)
""",
responses = {
@ApiResponse(
responseCode = "200",
description = "Liste paginée des gains récupérée avec succès",
content = @Content(
mediaType = "application/json",
examples = @ExampleObject(
value = """
{
"content": [
{
"id": 1,
"user": { "id": 5, "email": "client@example.com" },
"prizeType": "COFFRET_39",
"claimed": false,
"createdAt": "2025-10-20T15:21:45"
}
],
"pageable": { "pageNumber": 0, "pageSize": 10 },
"totalElements": 124
}
"""
)
)
)
}
)
@GetMapping
public ResponseEntity<Page<Win>> getAllWins(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
Pageable pageable = PageRequest.of(page, size);
Page<Win> wins = winRepository.findAll(pageable);
return ResponseEntity.ok(wins);
}
// =========================================================
// 2️⃣ Création manuelle d’un gain
// =========================================================
@Operation(
summary = "Créer un gain manuellement",
description = """
Permet à un administrateur de **créer un gain pour un utilisateur existant**.
Cette opération est utile pour :
- Simuler des cas de test,
- Ajouter un gain oublié,
- Corriger une erreur de saisie.
Le champ `user.id` doit être renseigné.
""",
responses = {
@ApiResponse(
responseCode = "200",
description = "Gain créé avec succès",
content = @Content(
mediaType = "application/json",
examples = @ExampleObject(
value = """
{
"id": 58,
"user": { "id": 3, "email": "client@example.com" },
"prizeType": "INFUSEUR",
"claimed": false,
"createdAt": "2025-10-27T13:50:10"
}
"""
)
)
),
@ApiResponse(
responseCode = "400",
description = "Utilisateur non trouvé ou non spécifié",
content = @Content(
mediaType = "application/json",
examples = @ExampleObject(value = "Utilisateur introuvable ou non spécifié")
)
)
}
)
@PostMapping
public ResponseEntity<?> createWin(@RequestBody Win newWin) {
if (newWin.getUser() == null || newWin.getUser().getId() == null) {
Map<String, String> error = new HashMap<>();
error.put("error", "Utilisateur non spécifié pour le gain");
error.put("message", "Utilisateur non spécifié pour le gain");
return ResponseEntity.badRequest().body(error);
}
Optional<User> user = userRepository.findById(newWin.getUser().getId());
if (user.isEmpty()) {
Map<String, String> error = new HashMap<>();
error.put("error", "Utilisateur introuvable");
error.put("message", "Utilisateur introuvable");
return ResponseEntity.badRequest().body(error);
}
newWin.setUser(user.get());
newWin.setClaimed(false);
newWin.setCreatedAt(LocalDateTime.now());
Win saved = winRepository.save(newWin);
return ResponseEntity.ok(saved);
}
// =========================================================
// 3️⃣ Gains d’un utilisateur spécifique
// =========================================================
@Operation(
summary = "Lister les gains d’un utilisateur",
description = """
Renvoie la liste complète des gains associés à un utilisateur donné.
Ce point d’accès est utilisé par les administrateurs et les employés de boutique pour la vérification des lots.
""",
responses = {
@ApiResponse(
responseCode = "200",
description = "Liste des gains récupérée avec succès",
content = @Content(
mediaType = "application/json",
examples = @ExampleObject(
value = """
[
{
"id": 5,
"prizeType": "COFFRET_99",
"claimed": false,
"createdAt": "2025-10-10T12:22:11"
},
{
"id": 9,
"prizeType": "INFUSEUR",
"claimed": true,
"claimedAt": "2025-10-14T10:03:52"
}
]
"""
)
)
),
@ApiResponse(
responseCode = "400",
description = "Utilisateur introuvable",
content = @Content(
mediaType = "application/json",
examples = @ExampleObject(value = "Utilisateur introuvable")
)
)
}
)
@GetMapping("/user/{id}")
public ResponseEntity<?> getWinsByUser(@PathVariable Long id) {
if (!userRepository.existsById(id)) {
Map<String, String> error = new HashMap<>();
error.put("error", "Utilisateur introuvable");
error.put("message", "Utilisateur introuvable");
return ResponseEntity.badRequest().body(error);
}
List<Win> wins = winRepository.findByUserId(id);
return ResponseEntity.ok(wins);
}
// =========================================================
// 4️⃣ Marquer un gain comme remis
// =========================================================
@Operation(
summary = "Marquer un gain comme remis",
description = """
Met à jour un gain pour indiquer qu’il a été **remis au gagnant**.
L’opération est effectuée par un employé ou un administrateur et enregistre la date de remise (`claimedAt`).
""",
responses = {
@ApiResponse(
responseCode = "200",
description = "Gain marqué comme remis avec succès",
content = @Content(
mediaType = "application/json",
examples = @ExampleObject(
value = """
{
"status": "success",
"message": "Le gain a été marqué comme remis.",
"claimedAt": "2025-10-27T14:12:09"
}
"""
)
)
),
@ApiResponse(
responseCode = "400",
description = "Gain introuvable ou déjà remis",
content = @Content(
mediaType = "application/json",
examples = @ExampleObject(
value = "Gain introuvable ou déjà remis"
)
)
)
}
)
@PatchMapping("/{id}/claim")
public ResponseEntity<?> claimWin(@PathVariable Long id, Authentication authentication) {
Optional<Win> optionalWin = winRepository.findById(id);
if (optionalWin.isEmpty()) {
Map<String, String> error = new HashMap<>();
error.put("error", "Gain introuvable");
error.put("message", "Gain introuvable");
return ResponseEntity.badRequest().body(error);
}
Win win = optionalWin.get();
if (win.isClaimed()) {
Map<String, String> error = new HashMap<>();
error.put("error", "Ce gain a déjà été remis");
error.put("message", "Ce gain a déjà été remis");
return ResponseEntity.badRequest().body(error);
}
win.setClaimed(true);
win.setClaimedAt(LocalDateTime.now());
winRepository.save(win);
return ResponseEntity.ok("Le gain a été marqué comme remis");
}
// =========================================================
// 5️⃣ Mettre à jour le choix de livraison d'un gain
// =========================================================
@Operation(
summary = "Mettre à jour le choix de livraison d'un gain",
description = """
Met à jour un gain avec le choix de livraison (domicile ou boutique) et l'adresse si nécessaire.
Cette opération est appelée après que l'utilisateur ait choisi son mode de réception.
""",
responses = {
@ApiResponse(
responseCode = "200",
description = "Choix de livraison mis à jour avec succès"
),
@ApiResponse(
responseCode = "400",
description = "Gain introuvable"
)
}
)
@PatchMapping("/{id}/delivery")
public ResponseEntity<?> updateDeliveryChoice(
@PathVariable Long id,
@RequestBody Map<String, Object> deliveryData,
Authentication authentication
) {
Optional<Win> optionalWin = winRepository.findById(id);
if (optionalWin.isEmpty()) {
Map<String, String> error = new HashMap<>();
error.put("error", "Gain introuvable");
error.put("message", "Gain introuvable");
return ResponseEntity.badRequest().body(error);
}
Win win = optionalWin.get();
// Vérifier que l'utilisateur peut modifier son propre gain
if (authentication != null && win.getUser() != null) {
String userEmail = authentication.getName();
if (!win.getUser().getEmail().equals(userEmail)) {
Map<String, String> error = new HashMap<>();
error.put("error", "Vous ne pouvez modifier que vos propres gains");
error.put("message", "Vous ne pouvez modifier que vos propres gains");
return ResponseEntity.status(403).body(error);
}
}
// Mettre à jour le type de livraison
if (deliveryData.containsKey("deliveryType")) {
win.setDeliveryType((String) deliveryData.get("deliveryType"));
}
// Mettre à jour l'adresse de livraison (en JSON)
if (deliveryData.containsKey("address")) {
Object addressObj = deliveryData.get("address");
if (addressObj != null) {
// Convertir l'objet en JSON string
try {
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
win.setDeliveryAddress(mapper.writeValueAsString(addressObj));
} catch (Exception e) {
// Si la conversion échoue, stocker comme string
win.setDeliveryAddress(addressObj.toString());
}
}
}
winRepository.save(win);
Map<String, Object> response = new HashMap<>();
response.put("status", "success");
response.put("message", "Choix de livraison mis à jour avec succès");
return ResponseEntity.ok(response);
}
// =========================================================
// 6️⃣ Récupérer les gains en livraison à domicile (non reçus)
// =========================================================
@Operation(
summary = "Lister les gains en livraison à domicile non reçus",
description = """
Renvoie la liste des gains qui ont été choisis pour la livraison à domicile
et qui n'ont pas encore été marqués comme reçus.
Utilisé par les administrateurs pour suivre les livraisons en cours.
""",
responses = {
@ApiResponse(
responseCode = "200",
description = "Liste des gains en livraison récupérée avec succès"
)
}
)
// Endpoint de test pour vérifier les permissions
@GetMapping("/test-auth")
public ResponseEntity<Map<String, Object>> testAuth(Authentication authentication) {
Map<String, Object> response = new HashMap<>();
response.put("authenticated", authentication != null);
response.put("name", authentication != null ? authentication.getName() : null);
response.put("authorities", authentication != null ?
authentication.getAuthorities().stream()
.map(a -> a.getAuthority())
.collect(java.util.stream.Collectors.toList()) : null);
response.put("hasAdminRole", authentication != null &&
authentication.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN")));
response.put("hasEmployeeRole", authentication != null &&
authentication.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals("ROLE_EMPLOYEE")));
return ResponseEntity.ok(response);
}
@GetMapping("/home-delivery/pending")
public ResponseEntity<List<Win>> getPendingHomeDeliveryWins() {
List<Win> wins = winRepository.findHomeDeliveryWinsNotReceived();
return ResponseEntity.ok(wins);
}
// =========================================================
// 7️⃣ Marquer un gain comme reçu (pour livraison à domicile)
// =========================================================
@Operation(
summary = "Marquer un gain comme reçu",
description = """
Met à jour un gain en livraison à domicile pour indiquer qu'il a été **reçu par le client**.
L'opération est effectuée par un administrateur et enregistre la date de réception (`receivedAt`).
""",
responses = {
@ApiResponse(
responseCode = "200",
description = "Gain marqué comme reçu avec succès"
),
@ApiResponse(
responseCode = "400",
description = "Gain introuvable ou déjà reçu"
)
}
)
@PatchMapping("/{id}/receive")
public ResponseEntity<?> markAsReceived(@PathVariable Long id, Authentication authentication) {
Optional<Win> optionalWin = winRepository.findById(id);
if (optionalWin.isEmpty()) {
Map<String, String> error = new HashMap<>();
error.put("error", "Gain introuvable");
error.put("message", "Gain introuvable");
return ResponseEntity.badRequest().body(error);
}
Win win = optionalWin.get();
// Vérifier que c'est bien un gain en livraison à domicile
if (!"home".equals(win.getDeliveryType())) {
Map<String, String> error = new HashMap<>();
error.put("error", "Ce gain n'est pas en livraison à domicile");
error.put("message", "Ce gain n'est pas en livraison à domicile");
return ResponseEntity.badRequest().body(error);
}
if (win.isReceived()) {
Map<String, String> error = new HashMap<>();
error.put("error", "Ce gain a déjà été marqué comme reçu");
error.put("message", "Ce gain a déjà été marqué comme reçu");
return ResponseEntity.badRequest().body(error);
}
win.setReceived(true);
win.setReceivedAt(LocalDateTime.now());
winRepository.save(win);
Map<String, Object> response = new HashMap<>();
response.put("status", "success");
response.put("message", "Le gain a été marqué comme reçu");
response.put("receivedAt", win.getReceivedAt());
return ResponseEntity.ok(response);
}
}