%%%------------------------------------------------------------------- %%% @doc Обработчик маршрута `/v1/events/:id/bookings`. %%% %%% POST – Создание бронирования (запись на событие). %%% GET – Получение списка бронирований события (для владельца календаря). %%% @end %%%------------------------------------------------------------------- -module(handler_bookings). -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). -spec handle(cowboy_req:req(), any()) -> {ok, cowboy_req:req(), any()}. handle(Req, _Opts) -> case cowboy_req:method(Req) of <<"POST">> -> create_booking(Req); <<"GET">> -> list_bookings(Req); _ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>) end. %%% Swagger metadata -spec trails() -> [map()]. trails() -> BookingSchema = booking_schema(), [ #{ % POST path => <<"/v1/events/:id/bookings">>, method => <<"POST">>, description => <<"Create a booking for an event">>, tags => [<<"Bookings">>], parameters => [ #{ name => <<"id">>, in => <<"path">>, description => <<"Event ID">>, required => true, schema => #{type => string} } ], responses => #{ 201 => #{ description => <<"Booking created">>, content => #{<<"application/json">> => #{schema => BookingSchema}} }, 400 => #{description => <<"Event is full or not active">>}, 403 => #{description => <<"Access denied">>}, 404 => #{description => <<"Event not found">>}, 409 => #{description => <<"Already booked">>} } }, #{ % GET list path => <<"/v1/events/:id/bookings">>, method => <<"GET">>, description => <<"List bookings for an event (owner only)">>, tags => [<<"Bookings">>], parameters => [ #{ name => <<"id">>, in => <<"path">>, description => <<"Event ID">>, required => true, schema => #{type => string} } ], responses => #{ 200 => #{ description => <<"Array of bookings">>, content => #{<<"application/json">> => #{schema => #{ type => array, items => BookingSchema }}} }, 403 => #{description => <<"Access denied">>}, 404 => #{description => <<"Event not found">>} } } ]. -spec booking_schema() -> map(). booking_schema() -> #{ type => object, properties => #{ id => #{type => string}, event_id => #{type => string}, user_id => #{type => string}, status => #{type => string, enum => [<<"pending">>, <<"confirmed">>, <<"cancelled">>, <<"expired">>]}, notes => #{type => string, nullable => true}, reminder_sent => #{type => boolean}, confirmed_at => #{type => string, format => <<"date-time">>, nullable => true}, created_at => #{type => string, format => <<"date-time">>}, updated_at => #{type => string, format => <<"date-time">>} } }. %%%=================================================================== %%% HTTP-методы %%%=================================================================== %% @doc POST /v1/events/:id/bookings — Создание бронирования. -spec create_booking(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}. create_booking(Req) -> case handler_utils:auth_user(Req) of {ok, UserId, Req1} -> EventId = cowboy_req:binding(id, Req1), case logic_booking:create_booking(UserId, EventId) of {ok, Booking} -> handler_utils:send_json(Req1, 201, booking_to_json(Booking)); {error, already_booked} -> handler_utils:send_error(Req1, 409, <<"Already booked">>); {error, full} -> handler_utils:send_error(Req1, 400, <<"Event is full">>); {error, event_full} -> handler_utils:send_error(Req1, 400, <<"Event is full">>); {error, event_not_active} -> handler_utils:send_error(Req1, 400, <<"Event is not active">>); {error, personal_calendar} -> handler_utils:send_error(Req1, 403, <<"personal_calendar">>); {error, subscription_inactive} -> handler_utils:send_error(Req1, 403, <<"subscription_inactive">>); {error, own_event} -> handler_utils:send_error(Req1, 403, <<"Cannot book own event">>); {error, access_denied} -> handler_utils:send_error(Req1, 403, <<"Access denied">>); {error, not_found} -> handler_utils:send_error(Req1, 404, <<"Event 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 GET /v1/events/:id/bookings — Список бронирований события (для владельца). -spec list_bookings(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}. list_bookings(Req) -> case handler_utils:auth_user(Req) of {ok, UserId, Req1} -> EventId = cowboy_req:binding(id, Req1), case logic_booking:list_event_bookings(UserId, EventId) of {ok, Bookings} -> Response = [booking_to_json(B) || B <- Bookings], handler_utils:send_json(Req1, 200, Response); {error, access_denied} -> handler_utils:send_error(Req1, 403, <<"Access denied">>); {error, not_found} -> handler_utils:send_error(Req1, 404, <<"Event 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. %%%=================================================================== %%% Внутренние функции %%%=================================================================== %% @private Формирует JSON-представление записи #booking{}. -spec booking_to_json(#booking{}) -> map(). booking_to_json(Booking) -> handler_utils:booking_to_json(Booking).