%%%------------------------------------------------------------------- %%% @doc Обработчик пользовательских тикетов (клиентский API). %%% %%% GET – получить список тикетов. %%% Администраторы видят все тикеты, %%% обычные пользователи – только свои. %%% POST – создать новый тикет об ошибке. %%% @end %%%------------------------------------------------------------------- -module(handler_tickets). -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() -> [ #{ % GET list path => <<"/v1/tickets">>, method => <<"GET">>, description => <<"List tickets (admin sees all, user sees own)">>, tags => [<<"Tickets">>], parameters => [ #{name => <<"limit">>, in => <<"query">>, schema => #{type => integer}, description => <<"Page size">>}, #{name => <<"offset">>, in => <<"query">>, schema => #{type => integer}, description => <<"Offset">>} ], responses => #{ 200 => #{ description => <<"Array of tickets">>, content => #{<<"application/json">> => #{schema => #{ type => array, items => ticket_schema() }}} }, 401 => #{description => <<"Unauthorized">>} } }, #{ % POST create path => <<"/v1/tickets">>, method => <<"POST">>, description => <<"Create a new ticket (bug report)">>, tags => [<<"Tickets">>], requestBody => #{ required => true, content => #{<<"application/json">> => #{schema => #{ type => object, required => [<<"error_message">>], properties => #{ error_message => #{type => string}, stacktrace => #{type => string}, context => #{type => string} } }}} }, responses => #{ 201 => #{description => <<"Ticket created">>}, 400 => #{description => <<"Missing required fields or invalid JSON">>}, 401 => #{description => <<"Unauthorized">>} } } ]. ticket_schema() -> #{ type => object, properties => #{ id => #{type => string}, reporter_id => #{type => string}, error_hash => #{type => string}, error_message => #{type => string}, stacktrace => #{type => string}, context => #{type => string}, count => #{type => integer}, first_seen => #{type => string, format => <<"date-time">>}, last_seen => #{type => string, format => <<"date-time">>}, status => #{type => string, enum => [<<"open">>, <<"in_progress">>, <<"resolved">>, <<"closed">>]}, assigned_to => #{type => string, nullable => true}, resolution_note => #{type => string, nullable => true} } }. %%%=================================================================== %%% HTTP-методы %%%=================================================================== %% @private -spec handle(cowboy_req:req(), any()) -> {ok, cowboy_req:req(), any()}. handle(Req, _Opts) -> case cowboy_req:method(Req) of <<"GET">> -> list_tickets(Req); <<"POST">> -> create_ticket(Req); _ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>) end. %% @doc GET /v1/tickets — список тикетов. -spec list_tickets(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}. list_tickets(Req) -> case handler_utils:auth_user(Req) of {ok, UserId, Req1} -> case admin_utils:is_admin(UserId) of true -> Tickets = core_ticket:list_all(), handler_utils:send_json(Req1, 200, [handler_utils:ticket_to_json(T) || T <- Tickets]); false -> Tickets = core_ticket:list_by_user(UserId), handler_utils:send_json(Req1, 200, [handler_utils:ticket_to_json(T) || T <- Tickets]) end; {error, Code, Message, Req1} -> handler_utils:send_error(Req1, Code, Message) end. %% @doc POST /v1/tickets — создание тикета. -spec create_ticket(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}. create_ticket(Req) -> case handler_utils:auth_user(Req) of {ok, UserId, Req1} -> {ok, Body, Req2} = cowboy_req:read_body(Req1), try jsx:decode(Body, [return_maps]) of #{<<"error_message">> := _} = Data -> TicketData = maps:merge( #{<<"reporter_id">> => UserId, <<"status">> => <<"open">>}, Data ), case core_ticket:create_ticket(TicketData) of {ok, Ticket} -> handler_utils:send_json(Req2, 201, handler_utils:ticket_to_json(Ticket)); {error, Reason} -> handler_utils:send_error(Req2, 500, Reason) end; _ -> handler_utils:send_error(Req2, 400, <<"Missing 'error_message' field">>) catch _:_ -> handler_utils:send_error(Req2, 400, <<"Invalid JSON">>) end; {error, Code, Message, Req1} -> handler_utils:send_error(Req1, Code, Message) end.