Skip to content

Système d'avis et d'évaluation de produits

Comment implémenter un système d'avis et d'évaluation de produits avec NENE2. Inclut la contrainte 1 utilisateur / 1 produit / 1 avis, l'agrégation des évaluations (moyenne, distribution) et les opérations CRUD.


Liste des endpoints

MéthodeCheminDescriptionAuth
POST/products/{productId}/reviewsPublier un avisRequis
GET/products/{productId}/reviewsListe des avis (curseur)Requis
GET/products/{productId}/reviews/summaryÉvaluation moyenne et distributionRequis
PUT/products/{productId}/reviews/{reviewId}Mettre à jour un avisPropriétaire uniquement
DELETE/products/{productId}/reviews/{reviewId}Supprimer un avisPropriétaire uniquement

Schéma DB

sql
CREATE TABLE reviews (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    product_id INTEGER NOT NULL,
    user_id INTEGER NOT NULL,
    rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5),
    body TEXT,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL,
    UNIQUE (product_id, user_id),
    FOREIGN KEY (product_id) REFERENCES products(id),
    FOREIGN KEY (user_id) REFERENCES users(id)
);

Contrainte 1 utilisateur / 1 produit / 1 avis

La contrainte UNIQUE(product_id, user_id) prévient les avis dupliqués au niveau de la couche données.

La couche applicative vérifie également en amont et retourne un 409 Conflict :

php
if ($this->repository->findByProductAndUser($productId, $userId) !== null) {
    return $this->json->create(['error' => 'already reviewed'], 409);
}

Après suppression, la republication est possible (la contrainte UNIQUE est levée).


Validation de l'évaluation

php
// is_int() rejette les nombres flottants (4.5 en JSON est un float → 422)
if (!isset($body['rating']) || !is_int($body['rating'])) {
    $errors[] = new ValidationError('rating', 'Rating must be an integer.', 'required');
} elseif ($body['rating'] < 1 || $body['rating'] > 5) {
    $errors[] = new ValidationError('rating', 'Rating must be between 1 and 5.', 'out_of_range');
}
ValeurRésultat
5 (int)Accepté
4.5 (float)422
0422
6422
Absent422

Agrégation des évaluations (Summary)

sql
-- Total et moyenne
SELECT COUNT(*) as total, AVG(rating) as avg_rating
FROM reviews WHERE product_id = ?

-- Distribution par étoile
SELECT COUNT(*) as cnt FROM reviews WHERE product_id = ? AND rating = ?

Exemple de réponse :

json
GET /products/1/reviews/summary

{
    "total": 150,
    "avg_rating": 4.23,
    "distribution": {
        "1": 5,
        "2": 8,
        "3": 20,
        "4": 52,
        "5": 65
    }
}

S'il n'y a aucun avis, "avg_rating": null.


Vérification de propriété (mise à jour / suppression)

php
$review = $this->repository->findReviewById($reviewId);
if ($review === null || (int) $review['product_id'] !== $productId) {
    return $this->json->create(['error' => 'review not found'], 404);
}
if ((int) $review['user_id'] !== $userId) {
    return $this->json->create(['error' => 'forbidden'], 403);
}

La vérification croisée du product_id retourne également 404 si un ID d'avis d'un autre produit est spécifié.


Pagination par curseur

GET /products/1/reviews?limit=20
→ { items: [...], next_cursor: 42 }

GET /products/1/reviews?limit=20&before_id=42
→ { items: [...], next_cursor: null }

next_cursor: null indique que la fin est atteinte.


Exemple d'implémentation (FT154)

/home/xi/docker/NENE2-FT/reviewlog/

Publié sous licence MIT.