%%%------------------------------------------------------------------- %%% @doc Обработчик конкретного отзыва (клиентский API). %%% %%% GET – получить отзыв по ID. %%% PUT – обновить отзыв (владельцем). %%% DELETE – удалить отзыв (владельцем). %%% @end %%%------------------------------------------------------------------- -module(handler_review_by_id). -behaviour(cowboy_handler). -export([init/2]). -export([trails/0]). -include("records.hrl"). %%% cowboy_handler callback -spec init(cowboy_req:req(), any()) -> {ok, cowboy_req:req(), any()}. init(Req, Opts) -> handle(Req, Opts). %%% Swagger metadata -spec trails() -> [map()]. trails() -> BaseParams = [ #{ name => <<"id">>, in => <<"path">>, description => <<"Review ID">>, required => true, schema => #{type => string} } ], [ #{ % GET by id path => <<"/v1/reviews/:id">>, method => <<"GET">>, description => <<"Get review by ID">>, tags => [<<"Reviews">>], parameters => BaseParams, responses => #{ 200 => #{ description => <<"Review details">>, content => #{<<"application/json">> => #{schema => review_schema()}} }, 403 => #{description => <<"Access denied">>}, 404 => #{description => <<"Review not found">>} } }, #{ % PUT update path => <<"/v1/reviews/:id">>, method => <<"PUT">>, description => <<"Update review">>, tags => [<<"Reviews">>], parameters => BaseParams, requestBody => #{ required => true, content => #{<<"application/json">> => #{schema => review_update_schema()}} }, responses => #{ 200 => #{description => <<"Review updated">>}, 400 => #{description => <<"Invalid request">>}, 403 => #{description => <<"Access denied">>}, 404 => #{description => <<"Review not found">>} } }, #{ % DELETE path => <<"/v1/reviews/:id">>, method => <<"DELETE">>, description => <<"Delete review">>, tags => [<<"Reviews">>], parameters => BaseParams, responses => #{ 200 => #{description => <<"Review deleted">>}, 403 => #{description => <<"Access denied">>}, 404 => #{description => <<"Review not found">>} } } ]. review_schema() -> #{ type => object, properties => #{ id => #{type => string}, user_id => #{type => string}, target_type => #{type => string, enum => [<<"calendar">>, <<"event">>]}, target_id => #{type => string}, rating => #{type => integer, minimum => 1, maximum => 5}, comment => #{type => string}, status => #{type => string, enum => [<<"visible">>, <<"hidden">>, <<"deleted">>]}, reason => #{type => string, nullable => true}, likes => #{type => integer}, dislikes => #{type => integer}, my_vote => #{type => string, enum => [<<"like">>, <<"dislike">>, null], nullable => true}, created_at => #{type => string, format => <<"date-time">>}, updated_at => #{type => string, format => <<"date-time">>} } }. review_update_schema() -> #{ type => object, properties => #{ rating => #{type => integer, minimum => 1, maximum => 5}, comment => #{type => string} } }. %%%=================================================================== %%% HTTP-методы %%%=================================================================== %% @private -spec handle(cowboy_req:req(), any()) -> {ok, cowboy_req:req(), any()}. handle(Req, _Opts) -> case cowboy_req:method(Req) of <<"GET">> -> get_review(Req); <<"PUT">> -> update_review(Req); <<"DELETE">> -> delete_review(Req); _ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>) end. %% @doc GET /v1/reviews/:id — получение отзыва. -spec get_review(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}. get_review(Req) -> case handler_utils:auth_user(Req) of {ok, UserId, Req1} -> ReviewId = cowboy_req:binding(id, Req1), case logic_review:get_review(UserId, ReviewId) of {ok, Review} -> MyVote = logic_review:my_vote(UserId, ReviewId), handler_utils:send_json(Req1, 200, handler_utils:review_to_json(Review, MyVote)); {error, access_denied} -> handler_utils:send_error(Req1, 403, <<"Access denied">>); {error, not_found} -> handler_utils:send_error(Req1, 404, <<"Review not found">>); {error, _} -> handler_utils:send_error(Req1, 500, <<"Internal server error">>) end; {error, Code, Message, Req1} -> handler_utils:send_error(Req1, Code, Message) end. %% @doc PUT /v1/reviews/:id — обновление отзыва. -spec update_review(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}. update_review(Req) -> case handler_utils:auth_user(Req) of {ok, UserId, Req1} -> ReviewId = cowboy_req:binding(id, Req1), {ok, Body, Req2} = cowboy_req:read_body(Req1), try jsx:decode(Body, [return_maps]) of UpdatesMap when is_map(UpdatesMap) -> Updates = maps:to_list(UpdatesMap), case logic_review:update_review(UserId, ReviewId, Updates) of {ok, _} -> case core_review:get_by_id(ReviewId) of {ok, Updated} -> MyVote = logic_review:my_vote(UserId, ReviewId), handler_utils:send_json(Req2, 200, handler_utils:review_to_json(Updated, MyVote)); _ -> handler_utils:send_error(Req2, 500, <<"Failed to retrieve updated review">>) end; {error, access_denied} -> handler_utils:send_error(Req2, 403, <<"Access denied">>); {error, not_found} -> handler_utils:send_error(Req2, 404, <<"Review not found">>); {error, _} -> handler_utils:send_error(Req2, 500, <<"Internal server error">>) end; _ -> handler_utils:send_error(Req2, 400, <<"Invalid JSON">>) catch _:_ -> handler_utils:send_error(Req2, 400, <<"Invalid JSON format">>) end; {error, Code, Message, Req1} -> handler_utils:send_error(Req1, Code, Message) end. %% @doc DELETE /v1/reviews/:id — удаление отзыва. -spec delete_review(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}. delete_review(Req) -> case handler_utils:auth_user(Req) of {ok, UserId, Req1} -> ReviewId = cowboy_req:binding(id, Req1), case logic_review:delete_review(UserId, ReviewId) of {ok, deleted} -> handler_utils:send_json(Req1, 200, #{status => <<"deleted">>}); {error, access_denied} -> handler_utils:send_error(Req1, 403, <<"Access denied">>); {error, not_found} -> handler_utils:send_error(Req1, 404, <<"Review not found">>); {error, _} -> handler_utils:send_error(Req1, 500, <<"Internal server error">>) end; {error, Code, Message, Req1} -> handler_utils:send_error(Req1, Code, Message) end.