P1: User API голосования за отзывы (like/dislike) #47
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Проблема
В модели
reviewуже есть счётчикиlikes/dislikes, клиент (EventHubFront) показывает их в UI, а вEventHubBackSpec§2.4 указано «реализованы лайки/дизлайки».Фактически нет user HTTP API для голосования:
POST/DELETE …/reviews/:id/…под голос нет (eventhub_app.erl: только/v1/reviewsи/v1/reviews/:id);review);my_voteтекущего пользователя;EventHubFrontSpec.md§7).Нужен полноценный контракт голосования + реализация + swagger + тесты + обновление спеки.
Влияние
Ожидаемый результат
Пользователь (auth JWT) может поставить/сменить/снять лайк или дизлайк на чужой видимый отзыв. Счётчики на
reviewобновляются атомарно. В ответах отзывов видно текущий голос вызывающего (my_vote). Контракт зафиксирован вclient-swagger.jsonиEventHubBackSpec.md.Контракт API (целевой)
База: user API
:8080, auth:Authorization: Bearer <access_token>(как остальные/v1/*).Content-Type:
application/json.1. Поставить / сменить голос
Path
idBody
valuelike\|dislikeПоведение
likesилиdislikes) на 1.value→ идемпотентно200, счётчики не менять.value→ сменить: уменьшить старый счётчик на 1, увеличить новый на 1 (не уходить ниже 0).403.status=deleted→404.hidden(и вызывающий не админ/мод) →404или403(выбрать один вариант и зафиксировать в swagger; предпочтительно404).401.400.Response
200review_idmy_votelike\|dislike\|nulllikesdislikes2. Снять голос
Поведение
200,my_vote: null.PUT.Response
200— тот же объект, что уPUT, сmy_vote: null.3. Расширение существующих ответов отзывов
Во всех user-ответах, где отдаётся review (как минимум):
GET /v1/reviews?target_type=&target_id=GET /v1/reviews/:idGET /v1/user/reviews(для чужих — не применимо; для своихmy_voteобычноnull)добавить поле:
my_votenullПример элемента списка:
edited_at— если уже отдаётся, сохранить как есть.Admin API менять не обязательно в рамках этой задачи (счётчики уже видны); при желании можно позже добавить админский просмотр голосов.
Модель данных (обязательно)
Счётчиков на
reviewнедостаточно: нужен per-user голос.Рекомендуемая сущность
review_vote(имя на усмотрение, смысл фиксирован):idreview_iduser_idvaluelike\|dislikecreated_atupdated_atУникальность:
(review_id, user_id)— один голос на пользователя на отзыв.Обновление
review.likes/review.dislikes— в одной транзакции с upsert/delete голоса (чтобы не было рассинхрона).Опционально (не блокер MVP): периодическая сверка счётчиков с
COUNT(*)поreview_vote.Ошибки (единый формат проекта)
Как в остальных handlers (кратко зафиксировать в swagger):
valueВне scope этой задачи
POST /v1/reportstarget_type=review) — уже есть.Критерии приёмки
PUT /v1/reviews/:id/voteиDELETE /v1/reviews/:id/voteзарегистрированы и работают с JWT.GET /v1/reviewsиGET /v1/reviews/:idестьmy_voteдля текущего пользователя.src/swagger/client-swagger.jsonи trails/handler schemas.EventHubSpec/EventHubBackSpec.md§2.4: явно описан HTTP-контракт (не только поля counters).EventHubFrontSpec.md§7 (отдельная задача на Front).Файлы (подсказка)
src/eventhub_app.erl— роутsrc/handlers/— новый handler или расширениеhandler_review_by_idsrc/core/core_review.erl+ новая core/logic для voteinclude/records.hrlsrc/swagger/client-swagger.json,src/swagger/eventhub_trails.erltest/api/.../test/unit/...EventHubSpec/EventHubBackSpec.mdКонтекст клиента
EventHubFront:ReviewsListуже показываетlikes/dislikes, кнопки голосования ждут API.EventHubFrontSpec.md§7: «лайки/дизлайки отзывов» в Future до появления user HTTP API.Приоритет
P1
Беру в работу. Сначала предложу варианты реализации, затем жду подтверждения.
Реализован вариант 1 (таблица review_vote + PUT/DELETE /v1/reviews/:id/vote).
Что сделано
review_vote, миграция20260719210000_review_vote, индексы review_id/user_idcore_review_vote: upsert/delete в одной транзакции со счётчиками likes/dislikesPUT/DELETE /v1/reviews/:id/vote; свой отзыв → 403, hidden/deleted → 404my_voteв GET /v1/reviews и GET /v1/reviews/:idТесты
rebar3 ct --suite=test/api_users_SUITE --case=user_test_review_vote— passedСделано и в master (
5b638de): PUT/DELETE /v1/reviews/:id/vote, my_vote, тесты, спека. Закрываю.