Refactor: Backend Frontend-Centric Auth - Suppression OIDC, validation JWT
Architecture modifiée pour Frontend-Centric Authentication: 1. **Suppression des dépendances OIDC** - quarkus-oidc → quarkus-smallrye-jwt - quarkus-keycloak-authorization → quarkus-smallrye-jwt-build - Le backend ne gère plus l'authentification OAuth 2. **Configuration JWT simple** - Validation des tokens JWT envoyés par le frontend - mp.jwt.verify.publickey.location (JWKS de Keycloak) - mp.jwt.verify.issuer (Keycloak realm) - Authentification via Authorization: Bearer header 3. **Suppression configurations OIDC** - application.properties: Suppression %dev.quarkus.oidc.* - application.properties: Suppression %prod.quarkus.oidc.* - application-prod.properties: Remplacement par mp.jwt.* - Logging: io.quarkus.oidc → io.quarkus.smallrye.jwt 4. **Sécurité simplifiée** - quarkus.security.auth.proactive=false - @Authenticated sur les endpoints - CORS configuré pour le frontend - Endpoints publics: /q/*, /openapi, /swagger-ui/* Flux d'authentification: 1️⃣ Frontend → Keycloak (OAuth login) 2️⃣ Frontend ← Keycloak (access_token) 3️⃣ Frontend → Backend (Authorization: Bearer token) 4️⃣ Backend valide le token JWT (signature + issuer) 5️⃣ Backend → Frontend (données API) Avantages: ✅ Pas de secret backend à gérer ✅ Pas de client btpxpress-backend dans Keycloak ✅ Séparation claire frontend/backend ✅ Backend devient une API REST stateless ✅ Tokens gérés par le frontend (localStorage/sessionStorage) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
package dev.lions.btpxpress.adapter.http;
|
||||
|
||||
import dev.lions.btpxpress.application.service.FournisseurService;
|
||||
import dev.lions.btpxpress.domain.core.entity.Fournisseur;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des fournisseurs BTP
|
||||
* Architecture 2025 : API complète pour la gestion des fournisseurs
|
||||
*/
|
||||
@Path("/api/v1/fournisseurs")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Fournisseurs", description = "Gestion des fournisseurs BTP")
|
||||
public class FournisseurResource {
|
||||
|
||||
@Inject
|
||||
FournisseurService fournisseurService;
|
||||
|
||||
// === ENDPOINTS DE LECTURE - ARCHITECTURE 2025 ===
|
||||
|
||||
@GET
|
||||
@Operation(summary = "Récupère tous les fournisseurs")
|
||||
@APIResponse(responseCode = "200", description = "Liste des fournisseurs")
|
||||
public Response getAllFournisseurs(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("10") int size,
|
||||
@QueryParam("search") String search) {
|
||||
try {
|
||||
List<Fournisseur> fournisseurs;
|
||||
if (search != null && !search.trim().isEmpty()) {
|
||||
fournisseurs = fournisseurService.searchFournisseurs(search);
|
||||
} else {
|
||||
fournisseurs = fournisseurService.getAllFournisseurs(page, size);
|
||||
}
|
||||
return Response.ok(fournisseurs).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la récupération des fournisseurs"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Récupère un fournisseur par ID")
|
||||
@APIResponse(responseCode = "200", description = "Fournisseur trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Fournisseur non trouvé")
|
||||
public Response getFournisseurById(
|
||||
@Parameter(description = "ID du fournisseur") @PathParam("id") UUID id) {
|
||||
try {
|
||||
Fournisseur fournisseur = fournisseurService.getFournisseurById(id);
|
||||
return Response.ok(fournisseur).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Fournisseur non trouvé"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/search")
|
||||
@Operation(summary = "Recherche des fournisseurs")
|
||||
@APIResponse(responseCode = "200", description = "Résultats de la recherche")
|
||||
public Response searchFournisseurs(@QueryParam("q") String query) {
|
||||
try {
|
||||
List<Fournisseur> fournisseurs = fournisseurService.searchFournisseurs(query);
|
||||
return Response.ok(fournisseurs).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la recherche"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/stats")
|
||||
@Operation(summary = "Récupère les statistiques des fournisseurs")
|
||||
@APIResponse(responseCode = "200", description = "Statistiques des fournisseurs")
|
||||
public Response getFournisseurStats() {
|
||||
try {
|
||||
Map<String, Object> stats = fournisseurService.getFournisseurStats();
|
||||
return Response.ok(stats).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors du calcul des statistiques"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE CRÉATION - ARCHITECTURE 2025 ===
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Crée un nouveau fournisseur")
|
||||
@APIResponse(responseCode = "201", description = "Fournisseur créé avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "409", description = "Conflit - fournisseur existant")
|
||||
public Response createFournisseur(@Valid Fournisseur fournisseur) {
|
||||
try {
|
||||
Fournisseur created = fournisseurService.createFournisseur(fournisseur);
|
||||
return Response.status(Response.Status.CREATED)
|
||||
.entity(created)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Erreur lors de la création du fournisseur"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Met à jour un fournisseur existant")
|
||||
@APIResponse(responseCode = "200", description = "Fournisseur mis à jour avec succès")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "404", description = "Fournisseur non trouvé")
|
||||
public Response updateFournisseur(
|
||||
@Parameter(description = "ID du fournisseur") @PathParam("id") UUID id,
|
||||
@Valid Fournisseur fournisseur) {
|
||||
try {
|
||||
Fournisseur updated = fournisseurService.updateFournisseur(id, fournisseur);
|
||||
return Response.ok(updated).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Fournisseur non trouvé"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Supprime un fournisseur")
|
||||
@APIResponse(responseCode = "204", description = "Fournisseur supprimé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Fournisseur non trouvé")
|
||||
public Response deleteFournisseur(
|
||||
@Parameter(description = "ID du fournisseur") @PathParam("id") UUID id) {
|
||||
try {
|
||||
fournisseurService.deleteFournisseur(id);
|
||||
return Response.noContent().build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Fournisseur non trouvé"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// === ENDPOINTS DE MODIFICATION - ARCHITECTURE 2025 ===
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/activate")
|
||||
@Operation(summary = "Active un fournisseur")
|
||||
@APIResponse(responseCode = "200", description = "Fournisseur activé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Fournisseur non trouvé")
|
||||
public Response activateFournisseur(
|
||||
@Parameter(description = "ID du fournisseur") @PathParam("id") UUID id) {
|
||||
try {
|
||||
fournisseurService.activateFournisseur(id);
|
||||
return Response.ok(Map.of("message", "Fournisseur activé avec succès")).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Fournisseur non trouvé"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/deactivate")
|
||||
@Operation(summary = "Désactive un fournisseur")
|
||||
@APIResponse(responseCode = "200", description = "Fournisseur désactivé avec succès")
|
||||
@APIResponse(responseCode = "404", description = "Fournisseur non trouvé")
|
||||
public Response deactivateFournisseur(
|
||||
@Parameter(description = "ID du fournisseur") @PathParam("id") UUID id) {
|
||||
try {
|
||||
fournisseurService.deactivateFournisseur(id);
|
||||
return Response.ok(Map.of("message", "Fournisseur désactivé avec succès")).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("error", "Fournisseur non trouvé"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user