Skip to content

Sistema de Avaliação e Comentários de Produto

Como implementar um sistema de avaliação e comentários de produto com NENE2. Inclui restrição de 1 usuário / 1 produto / 1 comentário, agregação de avaliações (média, distribuição) e CRUD.


Lista de Endpoints

MétodoCaminhoDescriçãoAuth
POST/products/{productId}/reviewsPublicar comentárioObrigatório
GET/products/{productId}/reviewsListar comentários (cursor)Obrigatório
GET/products/{productId}/reviews/summaryAvaliação média e distribuiçãoObrigatório
PUT/products/{productId}/reviews/{reviewId}Atualizar comentárioApenas próprio
DELETE/products/{productId}/reviews/{reviewId}Excluir comentárioApenas próprio

Schema do Banco de Dados

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)
);

Imposição de 1 Usuário / 1 Produto / 1 Comentário

A restrição UNIQUE(product_id, user_id) previne comentários duplicados na camada de dados.

Verificação prévia também na camada de aplicação, retornando 409 Conflict:

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

Após a exclusão, é possível postar novamente (a restrição UNIQUE é liberada).


Validação de Avaliação

php
// is_int() rejeita números de ponto flutuante (4.5 em JSON é 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');
}
ValorResultado
5 (int)Passa
4.5 (float)422
0422
6422
Omitido422

Agregação de Avaliações (Summary)

sql
-- Total e média
SELECT COUNT(*) as total, AVG(rating) as avg_rating
FROM reviews WHERE product_id = ?

-- Distribuição por estrela
SELECT COUNT(*) as cnt FROM reviews WHERE product_id = ? AND rating = ?

Exemplo de resposta:

json
GET /products/1/reviews/summary

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

Quando não há comentários, "avg_rating": null.


Verificação de Propriedade (Atualizar/Excluir)

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);
}

A verificação cruzada de product_id também retorna 404 quando um ID de comentário de outro produto é especificado.


Paginação por Cursor

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 indica o fim da lista.


Exemplo de Implementação (FT154)

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

Publicado sob a licença MIT.