fix: require calendar owner or admin for booking confirm/decline. Fixes EventHub/EventHubBack#53
This commit is contained in:
Regular → Executable
+1
@@ -64,6 +64,7 @@ trails() ->
|
|||||||
responses => #{
|
responses => #{
|
||||||
200 => #{description => <<"Booking updated">>},
|
200 => #{description => <<"Booking updated">>},
|
||||||
400 => #{description => <<"Invalid action">>},
|
400 => #{description => <<"Invalid action">>},
|
||||||
|
403 => #{description => <<"Access denied">>},
|
||||||
404 => #{description => <<"Booking not found">>}
|
404 => #{description => <<"Booking not found">>}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Regular → Executable
+61
-24
@@ -32,31 +32,54 @@ create_booking(UserId, EventId) ->
|
|||||||
|
|
||||||
%%%-------------------------------------------------------------------
|
%%%-------------------------------------------------------------------
|
||||||
%%% @doc Подтверждение бронирования (двухарная версия).
|
%%% @doc Подтверждение бронирования (двухарная версия).
|
||||||
|
%%% Только владелец календаря события или admin.
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-------------------------------------------------------------------
|
%%%-------------------------------------------------------------------
|
||||||
-spec confirm_booking(BookingId :: binary(), UserId :: binary()) ->
|
-spec confirm_booking(BookingId :: binary(), UserId :: binary()) ->
|
||||||
{ok, #booking{}} | {error, not_found | access_denied}.
|
{ok, #booking{}} | {error, not_found | access_denied}.
|
||||||
confirm_booking(BookingId, _UserId) ->
|
confirm_booking(BookingId, UserId) ->
|
||||||
case core_booking:get_by_id(BookingId) of
|
case core_booking:get_by_id(BookingId) of
|
||||||
{ok, Booking} ->
|
{ok, Booking} ->
|
||||||
case Booking#booking.status of
|
case can_manage_event_bookings(UserId, Booking#booking.event_id) of
|
||||||
pending ->
|
true ->
|
||||||
Now = calendar:universal_time(),
|
case Booking#booking.status of
|
||||||
core_booking:update(BookingId, [{status, confirmed}, {confirmed_at, Now}]);
|
pending ->
|
||||||
_ ->
|
Now = calendar:universal_time(),
|
||||||
{error, access_denied}
|
core_booking:update(BookingId, [{status, confirmed}, {confirmed_at, Now}]);
|
||||||
|
_ ->
|
||||||
|
{error, access_denied}
|
||||||
|
end;
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
end;
|
end;
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%%-------------------------------------------------------------------
|
%%%-------------------------------------------------------------------
|
||||||
%%% @doc Подтверждение бронирования (трёхарная версия для обработчиков).
|
%%% @doc Подтверждение или отклонение бронирования (для обработчиков).
|
||||||
|
%%% `decline` переводит pending-заявку в `cancelled`.
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-------------------------------------------------------------------
|
%%%-------------------------------------------------------------------
|
||||||
-spec confirm_booking(UserId :: binary(), BookingId :: binary(), confirm) ->
|
-spec confirm_booking(UserId :: binary(), BookingId :: binary(), confirm | decline) ->
|
||||||
{ok, #booking{}} | {error, not_found | access_denied}.
|
{ok, #booking{}} | {error, not_found | access_denied}.
|
||||||
confirm_booking(UserId, BookingId, confirm) ->
|
confirm_booking(UserId, BookingId, confirm) ->
|
||||||
confirm_booking(BookingId, UserId).
|
confirm_booking(BookingId, UserId);
|
||||||
|
confirm_booking(UserId, BookingId, decline) ->
|
||||||
|
case core_booking:get_by_id(BookingId) of
|
||||||
|
{ok, Booking} ->
|
||||||
|
case can_manage_event_bookings(UserId, Booking#booking.event_id) of
|
||||||
|
true ->
|
||||||
|
case Booking#booking.status of
|
||||||
|
pending ->
|
||||||
|
core_booking:update(BookingId, [{status, cancelled}]);
|
||||||
|
_ ->
|
||||||
|
{error, access_denied}
|
||||||
|
end;
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
|
end;
|
||||||
|
Error -> Error
|
||||||
|
end.
|
||||||
|
|
||||||
%%%-------------------------------------------------------------------
|
%%%-------------------------------------------------------------------
|
||||||
%%% @doc Отмена бронирования (двухарная версия).
|
%%% @doc Отмена бронирования (двухарная версия).
|
||||||
@@ -167,20 +190,11 @@ list_event_bookings(EventId) ->
|
|||||||
-spec list_event_bookings(UserId :: binary(), EventId :: binary()) ->
|
-spec list_event_bookings(UserId :: binary(), EventId :: binary()) ->
|
||||||
{ok, [#booking{}]} | {error, not_found | access_denied}.
|
{ok, [#booking{}]} | {error, not_found | access_denied}.
|
||||||
list_event_bookings(UserId, EventId) ->
|
list_event_bookings(UserId, EventId) ->
|
||||||
case core_event:get_by_id(EventId) of
|
case can_manage_event_bookings(UserId, EventId) of
|
||||||
{ok, Event} ->
|
true ->
|
||||||
case core_calendar:get_by_id(Event#event.calendar_id) of
|
core_booking:list_by_event(EventId);
|
||||||
{ok, Calendar} ->
|
{error, Reason} ->
|
||||||
case admin_utils:is_admin(UserId)
|
{error, Reason}
|
||||||
orelse Calendar#calendar.owner_id =:= UserId of
|
|
||||||
true -> core_booking:list_by_event(EventId);
|
|
||||||
false -> {error, access_denied}
|
|
||||||
end;
|
|
||||||
{error, not_found} ->
|
|
||||||
{error, not_found}
|
|
||||||
end;
|
|
||||||
{error, not_found} ->
|
|
||||||
{error, not_found}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%%-------------------------------------------------------------------
|
%%%-------------------------------------------------------------------
|
||||||
@@ -195,6 +209,29 @@ list_bookings_admin() ->
|
|||||||
%%% ВНУТРЕННИЕ ФУНКЦИИ
|
%%% ВНУТРЕННИЕ ФУНКЦИИ
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
|
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% @doc Владелец календаря события или admin может управлять заявками.
|
||||||
|
%%% @end
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
-spec can_manage_event_bookings(UserId :: binary(), EventId :: binary()) ->
|
||||||
|
true | {error, not_found | access_denied}.
|
||||||
|
can_manage_event_bookings(UserId, EventId) ->
|
||||||
|
case core_event:get_by_id(EventId) of
|
||||||
|
{ok, Event} ->
|
||||||
|
case core_calendar:get_by_id(Event#event.calendar_id) of
|
||||||
|
{ok, Calendar} ->
|
||||||
|
case Calendar#calendar.owner_id =:= UserId
|
||||||
|
orelse admin_utils:is_admin(UserId) of
|
||||||
|
true -> true;
|
||||||
|
false -> {error, access_denied}
|
||||||
|
end;
|
||||||
|
{error, not_found} ->
|
||||||
|
{error, not_found}
|
||||||
|
end;
|
||||||
|
{error, not_found} ->
|
||||||
|
{error, not_found}
|
||||||
|
end.
|
||||||
|
|
||||||
%%%-------------------------------------------------------------------
|
%%%-------------------------------------------------------------------
|
||||||
%%% @doc Проверка вместимости события.
|
%%% @doc Проверка вместимости события.
|
||||||
%%% `undefined` и `0` означают неограниченную вместимость.
|
%%% `undefined` и `0` означают неограниченную вместимость.
|
||||||
|
|||||||
Regular → Executable
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
-include("records.hrl").
|
-include("records.hrl").
|
||||||
|
|
||||||
-define(TABLES, [user, calendar, event, booking]).
|
-define(TABLES, [user, calendar, event, booking, admin]).
|
||||||
|
|
||||||
setup() ->
|
setup() ->
|
||||||
eh_test_support:start_mnesia(),
|
eh_test_support:start_mnesia(),
|
||||||
|
|||||||
Regular → Executable
+60
-1
@@ -25,6 +25,11 @@ logic_booking_test_() ->
|
|||||||
{"Create booking when event is full", fun test_booking_event_full/0},
|
{"Create booking when event is full", fun test_booking_event_full/0},
|
||||||
{"Pending bookings do not fill capacity", fun test_pending_does_not_fill/0},
|
{"Pending bookings do not fill capacity", fun test_pending_does_not_fill/0},
|
||||||
{"Confirm booking", fun test_confirm_booking/0},
|
{"Confirm booking", fun test_confirm_booking/0},
|
||||||
|
{"Confirm booking as booker denied", fun test_confirm_booker_denied/0},
|
||||||
|
{"Confirm booking as stranger denied", fun test_confirm_stranger_denied/0},
|
||||||
|
{"Confirm booking as admin", fun test_confirm_booking_admin/0},
|
||||||
|
{"Decline booking by owner", fun test_decline_booking/0},
|
||||||
|
{"Decline booking as booker denied", fun test_decline_booker_denied/0},
|
||||||
{"Confirm non-pending booking denied", fun test_confirm_non_pending/0},
|
{"Confirm non-pending booking denied", fun test_confirm_non_pending/0},
|
||||||
{"Cancel booking by participant", fun test_cancel_booking/0},
|
{"Cancel booking by participant", fun test_cancel_booking/0},
|
||||||
{"Cancel booking access denied", fun test_cancel_access_denied/0},
|
{"Cancel booking access denied", fun test_cancel_access_denied/0},
|
||||||
@@ -49,6 +54,11 @@ create_test_user(Role) ->
|
|||||||
mnesia:dirty_write(User),
|
mnesia:dirty_write(User),
|
||||||
UserId.
|
UserId.
|
||||||
|
|
||||||
|
create_test_admin() ->
|
||||||
|
AdminId = base64:encode(crypto:strong_rand_bytes(16), #{mode => urlsafe, padding => false}),
|
||||||
|
Admin = eh_test_support:seed_admin(#{id => AdminId, email => <<AdminId/binary, "@admin.test">>}),
|
||||||
|
Admin#admin.id.
|
||||||
|
|
||||||
create_test_calendar(OwnerId, Confirmation) ->
|
create_test_calendar(OwnerId, Confirmation) ->
|
||||||
{ok, Calendar} = core_calendar:create(OwnerId, <<"Test Calendar">>, <<"">>, Confirmation),
|
{ok, Calendar} = core_calendar:create(OwnerId, <<"Test Calendar">>, <<"">>, Confirmation),
|
||||||
Calendar#calendar.id.
|
Calendar#calendar.id.
|
||||||
@@ -98,7 +108,7 @@ test_booking_event_full() ->
|
|||||||
EventId = create_test_event_with_capacity(CalendarId, 1),
|
EventId = create_test_event_with_capacity(CalendarId, 1),
|
||||||
|
|
||||||
{ok, B1} = logic_booking:create_booking(Participant1Id, EventId),
|
{ok, B1} = logic_booking:create_booking(Participant1Id, EventId),
|
||||||
{ok, _} = logic_booking:confirm_booking(Participant1Id, B1#booking.id, confirm),
|
{ok, _} = logic_booking:confirm_booking(OwnerId, B1#booking.id, confirm),
|
||||||
{error, full} = logic_booking:create_booking(Participant2Id, EventId).
|
{error, full} = logic_booking:create_booking(Participant2Id, EventId).
|
||||||
|
|
||||||
test_pending_does_not_fill() ->
|
test_pending_does_not_fill() ->
|
||||||
@@ -122,6 +132,55 @@ test_confirm_booking() ->
|
|||||||
{ok, Confirmed} = logic_booking:confirm_booking(OwnerId, Booking#booking.id, confirm),
|
{ok, Confirmed} = logic_booking:confirm_booking(OwnerId, Booking#booking.id, confirm),
|
||||||
?assertEqual(confirmed, Confirmed#booking.status).
|
?assertEqual(confirmed, Confirmed#booking.status).
|
||||||
|
|
||||||
|
test_confirm_booker_denied() ->
|
||||||
|
OwnerId = create_test_user(user),
|
||||||
|
ParticipantId = create_test_user(user),
|
||||||
|
CalendarId = create_test_calendar(OwnerId, manual),
|
||||||
|
EventId = create_test_event(CalendarId),
|
||||||
|
|
||||||
|
{ok, Booking} = logic_booking:create_booking(ParticipantId, EventId),
|
||||||
|
{error, access_denied} = logic_booking:confirm_booking(ParticipantId, Booking#booking.id, confirm).
|
||||||
|
|
||||||
|
test_confirm_stranger_denied() ->
|
||||||
|
OwnerId = create_test_user(user),
|
||||||
|
ParticipantId = create_test_user(user),
|
||||||
|
StrangerId = create_test_user(user),
|
||||||
|
CalendarId = create_test_calendar(OwnerId, manual),
|
||||||
|
EventId = create_test_event(CalendarId),
|
||||||
|
|
||||||
|
{ok, Booking} = logic_booking:create_booking(ParticipantId, EventId),
|
||||||
|
{error, access_denied} = logic_booking:confirm_booking(StrangerId, Booking#booking.id, confirm).
|
||||||
|
|
||||||
|
test_confirm_booking_admin() ->
|
||||||
|
OwnerId = create_test_user(user),
|
||||||
|
ParticipantId = create_test_user(user),
|
||||||
|
AdminId = create_test_admin(),
|
||||||
|
CalendarId = create_test_calendar(OwnerId, manual),
|
||||||
|
EventId = create_test_event(CalendarId),
|
||||||
|
|
||||||
|
{ok, Booking} = logic_booking:create_booking(ParticipantId, EventId),
|
||||||
|
{ok, Confirmed} = logic_booking:confirm_booking(AdminId, Booking#booking.id, confirm),
|
||||||
|
?assertEqual(confirmed, Confirmed#booking.status).
|
||||||
|
|
||||||
|
test_decline_booking() ->
|
||||||
|
OwnerId = create_test_user(user),
|
||||||
|
ParticipantId = create_test_user(user),
|
||||||
|
CalendarId = create_test_calendar(OwnerId, manual),
|
||||||
|
EventId = create_test_event(CalendarId),
|
||||||
|
|
||||||
|
{ok, Booking} = logic_booking:create_booking(ParticipantId, EventId),
|
||||||
|
{ok, Declined} = logic_booking:confirm_booking(OwnerId, Booking#booking.id, decline),
|
||||||
|
?assertEqual(cancelled, Declined#booking.status).
|
||||||
|
|
||||||
|
test_decline_booker_denied() ->
|
||||||
|
OwnerId = create_test_user(user),
|
||||||
|
ParticipantId = create_test_user(user),
|
||||||
|
CalendarId = create_test_calendar(OwnerId, manual),
|
||||||
|
EventId = create_test_event(CalendarId),
|
||||||
|
|
||||||
|
{ok, Booking} = logic_booking:create_booking(ParticipantId, EventId),
|
||||||
|
{error, access_denied} = logic_booking:confirm_booking(ParticipantId, Booking#booking.id, decline).
|
||||||
|
|
||||||
test_confirm_non_pending() ->
|
test_confirm_non_pending() ->
|
||||||
OwnerId = create_test_user(user),
|
OwnerId = create_test_user(user),
|
||||||
ParticipantId = create_test_user(user),
|
ParticipantId = create_test_user(user),
|
||||||
|
|||||||
Reference in New Issue
Block a user