ContactApiController
extends AbstractController
in package
Contrôleur d'API du carnet de contacts personnel.
Il gère le cycle de vie complet des contacts appartenant à l'utilisateur authentifié
(liste paginée, recherche, création, mise à jour, suppression) ainsi que deux modes
d'acquisition automatisés : la conversion d'une carte de visite numérique BCard en
contact, et le scan d'une carte de visite papier analysée par AIAnalysisService.
Aucun attribut #[IsGranted] n'est utilisé : chaque action vérifie elle-même la
présence d'un utilisateur authentifié et la propriété du contact ciblé, un contact
appartenant à autrui étant masqué derrière une 404.
Préfixe de route : /api/contacts. Aucun rôle exigé au niveau de la classe.
Attributes
- #[Route]
- '/api/contacts'
Table of Contents
Properties
- $contactRepository : ContactRepository
- $entityManager : EntityManagerInterface
- $serializer : SerializerInterface
- $validator : ValidatorInterface
Methods
- __construct() : mixed
- create() : JsonResponse
- Crée un nouveau contact
- createFromBusinessCard() : JsonResponse
- Crée automatiquement un contact à partir d'une business card
- delete() : JsonResponse
- Supprime un contact
- list() : JsonResponse
- Récupère la liste paginée des contacts de l'utilisateur connecté
- scanPaperCard() : JsonResponse
- Scanner une carte de visite papier avec l'IA
- search() : JsonResponse
- Recherche des contacts
- show() : JsonResponse
- Récupère un contact spécifique
- update() : JsonResponse
- Met à jour un contact existant
Properties
$contactRepository read-only
private
ContactRepository
$contactRepository
$entityManager read-only
private
EntityManagerInterface
$entityManager
$serializer read-only
private
SerializerInterface
$serializer
$validator read-only
private
ValidatorInterface
$validator
Methods
__construct()
public
__construct(EntityManagerInterface $entityManager, ContactRepository $contactRepository, SerializerInterface $serializer, ValidatorInterface $validator) : mixed
Parameters
- $entityManager : EntityManagerInterface
-
Gestionnaire d'entités Doctrine, utilisé pour persister et supprimer les contacts.
- $contactRepository : ContactRepository
-
Dépôt des contacts (recherche, comptage, contrôles de doublon).
- $serializer : SerializerInterface
-
Sérialiseur utilisé pour désérialiser le corps JSON en entité Contact.
- $validator : ValidatorInterface
-
Validateur appliqué aux contacts avant persistance.
create()
Crée un nouveau contact
public
create(Request $request) : JsonResponse
Désérialise le corps JSON directement en entité Contact, puis applique deux contrôles d'unicité limités au carnet de l'utilisateur courant : un contact portant le même email, puis un contact portant le même téléphone. Les deux contrôles ne sont effectués que si la valeur correspondante est renseignée. Le contact est ensuite rattaché à l'utilisateur authentifié, validé, puis persisté.
Route : /api/contacts (POST), nom api_contacts_create.
Corps attendu : objet JSON dont les propriétés correspondent aux champs de
l'entité Contact, par exemple
{"firstname": "string", "lastname": "string", "email": "string — contrôlé en unicité", "phone": "string — contrôlé en unicité", "company": "string", "jobTitle": "string", "note": "string"}. Toutes les clés sont optionnelles côté désérialisation ;
les obligations réelles proviennent des contraintes de validation de l'entité.
Réponses : 201 contact créé, 400 corps JSON invalide ({"error": "Invalid JSON data"}),
400 violations de contraintes ({"errors": {propriété: message}}), 409 email déjà
présent dans le carnet, 409 téléphone déjà présent, 401 appelant non authentifié.
Parameters
- $request : Request
-
Requête HTTP dont le corps porte le contact à créer.
Attributes
- #[Route]
- ''
- $name: 'api_contacts_create'
- $methods: ['POST']
Return values
JsonResponse —Contact créé, sérialisé avec le groupe contact:read, ou erreurs.
createFromBusinessCard()
Crée automatiquement un contact à partir d'une business card
public
createFromBusinessCard(string $hash, BusinessCardAizeRepository $businessCardRepository) : JsonResponse
La carte est d'abord recherchée par son publicHash. En cas d'échec, un repli
historique tente une recherche par identifiant numérique, uniquement si la valeur
est numérique et fait moins de 20 caractères — précaution destinée à éviter qu'un
hash entièrement numérique ne soit interprété comme un identifiant. Un contrôle
d'unicité sur l'email est ensuite appliqué au carnet de l'appelant. Le contact créé
reprend nom, prénom, email, entreprise et fonction de la carte ; le téléphone retenu
est le premier numéro visible, avec repli sur le numéro principal. Le contact est
marqué isBCard = true et reçoit une note mentionnant l'identifiant de la BCard
d'origine. Aucune validation explicite n'est exécutée avant la persistance.
Route : /api/contacts/from-bcard/{hash} (POST), nom api_contacts_create_from_bcard.
Corps attendu : aucun ; la source est identifiée par le segment {hash}.
Réponses : 201 contact créé, 404 business card introuvable par hash ou par identifiant, 409 contact déjà présent avec le même email, 401 appelant non authentifié.
Parameters
- $hash : string
-
Hash public de la carte, ou identifiant numérique en mode de compatibilité.
- $businessCardRepository : BusinessCardAizeRepository
-
Dépôt utilisé pour retrouver la carte source.
Attributes
- #[Post]
- $path: '/api/contacts/from-bcard/{hash}'
- $description: 'Crée automatiquement un contact à partir d\'une business card existante'
- $summary: 'Créer un contact depuis une BCard'
- $tags: ['Contacts']
- $parameters: [new OA\Parameter(name: 'hash', description: 'Hash public de la business card à convertir en contact', in: 'path', required: true, schema: new OA\Schema(type: 'string'))]
- $responses: [new OA\Response(response: 201, description: 'Contact créé avec succès'), new OA\Response(response: 404, description: 'Business card non trouvée'), new OA\Response(response: 409, description: 'Un contact avec cet email existe déjà'), new OA\Response(response: 401, description: 'Non authentifié')]
- #[Route]
- '/from-bcard/{hash}'
- $name: 'api_contacts_create_from_bcard'
- $methods: ['POST']
Return values
JsonResponse —Contact créé, sérialisé avec le groupe contact:read, ou message d'erreur.
delete()
Supprime un contact
public
delete(Contact $contact) : JsonResponse
Après contrôle de propriété, l'entité est retirée définitivement de la base (suppression physique, sans archivage ni corbeille). Aucun corps n'est renvoyé.
Route : /api/contacts/{id} (DELETE), nom api_contacts_delete.
Réponses : 204 contact supprimé (corps vide), 404 contact inexistant, appartenant à autrui, ou appelant non authentifié.
Parameters
- $contact : Contact
-
Contact résolu depuis le paramètre de route
{id}.
Attributes
- #[Route]
- '/{id}'
- $name: 'api_contacts_delete'
- $methods: ['DELETE']
Return values
JsonResponse —Réponse sans contenu, ou message d'erreur.
list()
Récupère la liste paginée des contacts de l'utilisateur connecté
public
list(Request $request) : JsonResponse
Délègue la pagination et le filtrage textuel au dépôt via
findByUserWithSearch(), puis recompte le total avec countByUser() en
appliquant le même terme de recherche afin que la pagination reste cohérente.
Les paramètres page et limit sont lus comme entiers sans borne haute ;
la sérialisation utilise le groupe contact:read.
Route : /api/contacts (GET), nom api_contacts_list.
Paramètres de requête : page (optionnel, défaut 1), limit (optionnel, défaut 20),
search (optionnel, défaut chaîne vide).
Réponses : 200 liste renvoyée, 401 aucun utilisateur authentifié
({"error": "Authentication required"}).
Parameters
- $request : Request
-
Requête HTTP portant les paramètres de pagination et de recherche.
Attributes
- #[Route]
- ''
- $name: 'api_contacts_list'
- $methods: ['GET']
Return values
JsonResponse —Objet JSON {contacts: list<Contact>, pagination: array<string, int|float>}.
scanPaperCard()
Scanner une carte de visite papier avec l'IA
public
scanPaperCard(Request $request, AIAnalysisService $aiAnalysisService, UploaderService $uploaderService) : JsonResponse
Reçoit une photo en multipart/form-data, vérifie qu'elle est présente, valide et
non vide, puis la soumet à AIAnalysisService::analyzeBusinessCard() avant
d'extraire les champs structurés via extractContactData(). Si l'email extrait
correspond à un contact déjà présent dans le carnet, l'opération est interrompue et
l'identifiant du contact existant est renvoyé. Sinon un contact est créé avec les
données extraites, chaque champ manquant étant remplacé par la chaîne
« Non spécifié » (y compris email et téléphone), activitySector et country
étant systématiquement forcés à cette même valeur. Les informations d'adresse, de
ville, de code postal, de pays et de site web sont concaténées dans la note.
La photo est enfin stockée dans contact_photo_directory et rattachée au contact.
Toute exception est convertie en réponse 400 ; le message est spécialisé lorsque
l'erreur mentionne un problème de JSON valide (photo jugée trop peu nette).
Route : /api/contacts/scan/paper (POST), nom api_contacts_scan_paper.
Corps attendu (multipart/form-data) : {"photo": "file — image de la carte de visite (obligatoire)"}
Réponses : 200 carte analysée et contact créé ({success: true, message, contact}),
400 photo absente, 400 contact déjà existant pour l'email extrait
({success: false, message, existing_contact_id}), 400 photo invalide ou vide,
analyse IA en échec ou upload en erreur ({success: false, message, error}),
401 appelant non authentifié.
Parameters
- $request : Request
-
Requête HTTP portant le fichier
photo. - $aiAnalysisService : AIAnalysisService
-
Service d'analyse IA de la carte et d'extraction des champs.
- $uploaderService : UploaderService
-
Service d'upload utilisé pour archiver la photo scannée.
Attributes
- #[Post]
- $path: '/api/contacts/scan/paper'
- $description: 'Scanner une carte de visite papier avec l\'IA pour extraire les informations de contact'
- $summary: 'Scanner une carte de visite papier'
- $requestBody: new OA\RequestBody(description: 'Photo de la carte de visite à analyser', required: true, content: new OA\MediaType(mediaType: 'multipart/form-data', schema: new OA\Schema(properties: [new OA\Property(property: 'photo', description: 'Fichier image de la carte de visite', type: 'string', format: 'binary')], type: 'object')))
- $tags: ['Contacts']
- $responses: [new OA\Response(response: 200, description: 'Carte analysée avec succès et contact créé', content: new OA\JsonContent(properties: [new OA\Property(property: 'success', type: 'boolean', example: true), new OA\Property(property: 'message', type: 'string', example: 'Contact papier analysé et ajouté avec succès !'), new OA\Property(property: 'contact', type: 'string', example: 'Entité contact serait retourné ici')])), new OA\Response(response: 400, description: 'Erreur lors de l\'analyse', content: new OA\JsonContent(properties: [new OA\Property(property: 'success', type: 'boolean', example: false), new OA\Property(property: 'message', type: 'string', example: 'Erreur lors de l\'analyse de la carte de visite'), new OA\Property(property: 'error', type: 'string', example: 'Détails de l\'erreur')])), new OA\Response(response: 401, description: 'Non authentifié'), new OA\Response(response: 403, description: 'Accès refusé')]
- #[Route]
- '/scan/paper'
- $name: 'api_contacts_scan_paper'
- $methods: ['POST']
Return values
JsonResponse —Objet JSON de succès contenant les données extraites, ou objet d'erreur.
search()
Recherche des contacts
public
search(Request $request) : JsonResponse
Recherche rapide restreinte au carnet de l'utilisateur authentifié, déléguée à
ContactRepository::searchByUser(). Le terme q est obligatoire : une chaîne
vide ou absente est rejetée. Le paramètre limit, non documenté dans l'attribut
OpenApi, borne le nombre de résultats (défaut 10). La réponse encapsule les
résultats sous la clé contacts, alors que le schéma OpenApi déclare un tableau
de premier niveau.
Attention : la route /{id} étant déclarée avant celle-ci, ce point d'entrée peut
être court-circuité par ContactApiController::show() lors du routage.
Route : /api/contacts/search (GET), nom api_contacts_search.
Paramètres de requête : q (obligatoire, terme recherché), limit (optionnel, défaut 10).
Réponses : 200 résultats renvoyés, 400 terme de recherche manquant ou vide, 401 appelant non authentifié.
Parameters
- $request : Request
-
Requête HTTP portant le terme de recherche et la limite.
Attributes
- #[Get]
- $path: '/api/contacts/search'
- $description: 'Recherche rapide de contacts par nom, prénom, email ou entreprise'
- $summary: 'Recherche rapide de contacts'
- $tags: ['Contacts']
- $parameters: [new OA\Parameter(name: 'q', description: 'Terme de recherche', in: 'query', required: true, schema: new OA\Schema(type: 'string'))]
- $responses: [new OA\Response(response: 200, description: 'Résultats de la recherche', content: new OA\JsonContent(type: 'array', items: new OA\Items(properties: [new OA\Property(property: 'id', type: 'integer'), new OA\Property(property: 'firstname', type: 'string'), new OA\Property(property: 'lastname', type: 'string'), new OA\Property(property: 'email', type: 'string'), new OA\Property(property: 'company', type: 'string'), new OA\Property(property: 'jobTitle', type: 'string'), new OA\Property(property: 'phone', type: 'string')]))), new OA\Response(response: 400, description: 'Terme de recherche manquant'), new OA\Response(response: 401, description: 'Non authentifié'), new OA\Response(response: 403, description: 'Accès refusé')]
- #[Route]
- '/search'
- $name: 'api_contacts_search'
- $methods: ['GET']
Return values
JsonResponse —Objet JSON {contacts: list<Contact>} sérialisé avec le groupe contact:read.
show()
Récupère un contact spécifique
public
show(Contact $contact) : JsonResponse
Le contact est résolu automatiquement par le ParamConverter à partir du segment
{id} (un identifiant inconnu provoque donc une 404 émise par Symfony avant
l'exécution de la méthode). Le contrôle de propriété qui suit renvoie également
une 404 — et non une 403 — lorsque le contact appartient à un autre utilisateur
ou que l'appelant n'est pas authentifié, afin de ne pas révéler son existence.
Route : /api/contacts/{id} (GET), nom api_contacts_show.
Réponses : 200 contact renvoyé, 404 contact inexistant, appartenant à autrui, ou appelant non authentifié.
Parameters
- $contact : Contact
-
Contact résolu depuis le paramètre de route
{id}.
Attributes
- #[Route]
- '/{id}'
- $name: 'api_contacts_show'
- $methods: ['GET']
Return values
JsonResponse —Contact sérialisé avec le groupe contact:read, ou message d'erreur.
update()
Met à jour un contact existant
public
update(Contact $contact, Request $request) : JsonResponse
Après contrôle de propriété, le corps JSON est désérialisé par-dessus l'entité
existante (object_to_populate) : seules les clés présentes sont écrasées, la
requête se comporte donc comme une mise à jour partielle malgré le verbe PUT.
Aucun contrôle d'unicité sur l'email ou le téléphone n'est effectué ici,
contrairement à ContactApiController::create(). La date de mise à jour est
repositionnée à l'instant courant avant validation et flush().
Route : /api/contacts/{id} (PUT), nom api_contacts_update.
Corps attendu : objet JSON partiel reprenant les champs de l'entité Contact
(firstname, lastname, email, phone, company, jobTitle, note, …).
Réponses : 200 contact mis à jour, 400 corps JSON invalide
({"error": "Invalid JSON data"}), 400 violations de contraintes
({"errors": {propriété: message}}), 404 contact inexistant, appartenant à autrui,
ou appelant non authentifié.
Parameters
- $contact : Contact
-
Contact résolu depuis le paramètre de route
{id}. - $request : Request
-
Requête HTTP dont le corps porte les champs à modifier.
Attributes
- #[Route]
- '/{id}'
- $name: 'api_contacts_update'
- $methods: ['PUT']
Return values
JsonResponse —Contact mis à jour, sérialisé avec le groupe contact:read, ou erreurs.