Stage 3.4

This commit is contained in:
2026-04-20 16:40:44 +03:00
parent 42a047a938
commit b24cbc97f3
25 changed files with 2520 additions and 123 deletions

102
src/core/core_booking.erl Normal file
View File

@@ -0,0 +1,102 @@
-module(core_booking).
-include("records.hrl").
-export([create/2, get_by_id/1, get_by_event_and_user/2, list_by_event/1, list_by_user/1]).
-export([update_status/2, delete/1]).
-export([generate_id/0]).
%% Создание бронирования
create(EventId, UserId) ->
Id = generate_id(),
Booking = #booking{
id = Id,
event_id = EventId,
user_id = UserId,
status = pending,
confirmed_at = undefined,
created_at = calendar:universal_time(),
updated_at = calendar:universal_time()
},
F = fun() ->
mnesia:write(Booking),
{ok, Booking}
end,
case mnesia:transaction(F) of
{atomic, Result} -> Result;
{aborted, Reason} -> {error, Reason}
end.
%% Получение бронирования по ID
get_by_id(Id) ->
case mnesia:dirty_read(booking, Id) of
[] -> {error, not_found};
[Booking] -> {ok, Booking}
end.
%% Получение бронирования по событию и пользователю
get_by_event_and_user(EventId, UserId) ->
Match = #booking{event_id = EventId, user_id = UserId, _ = '_'},
case mnesia:dirty_match_object(Match) of
[] -> {error, not_found};
[Booking] -> {ok, Booking}
end.
%% Список бронирований события
list_by_event(EventId) ->
Match = #booking{event_id = EventId, _ = '_'},
Bookings = mnesia:dirty_match_object(Match),
{ok, Bookings}.
%% Список бронирований пользователя
list_by_user(UserId) ->
Match = #booking{user_id = UserId, _ = '_'},
Bookings = mnesia:dirty_match_object(Match),
{ok, Bookings}.
%% Обновление статуса бронирования
update_status(Id, Status) when Status =:= pending; Status =:= confirmed; Status =:= cancelled ->
F = fun() ->
case mnesia:read(booking, Id) of
[] ->
{error, not_found};
[Booking] ->
Updated = Booking#booking{
status = Status,
confirmed_at = case Status of
confirmed -> calendar:universal_time();
_ -> Booking#booking.confirmed_at
end,
updated_at = calendar:universal_time()
},
mnesia:write(Updated),
{ok, Updated}
end
end,
case mnesia:transaction(F) of
{atomic, Result} -> Result;
{aborted, Reason} -> {error, Reason}
end.
%% Удаление бронирования (hard delete)
delete(Id) ->
F = fun() ->
case mnesia:read(booking, Id) of
[] ->
{error, not_found};
[Booking] ->
mnesia:delete_object(Booking),
{ok, deleted}
end
end,
case mnesia:transaction(F) of
{atomic, Result} -> Result;
{aborted, Reason} -> {error, Reason}
end.
%% Внутренние функции
generate_id() ->
base64:encode(crypto:strong_rand_bytes(16), #{mode => urlsafe, padding => false}).

View File

@@ -1,11 +1,11 @@
-module(core_calendar).
-include("records.hrl").
-export([create/3, get_by_id/1, list_by_owner/1, update/2, delete/1]).
-export([create/4, get_by_id/1, list_by_owner/1, update/2, delete/1]).
-export([generate_id/0]).
%% Создание календаря
create(OwnerId, Title, Description) ->
create(OwnerId, Title, Description, Confirmation) ->
Id = generate_id(),
Calendar = #calendar{
id = Id,
@@ -14,7 +14,7 @@ create(OwnerId, Title, Description) ->
description = Description,
tags = [],
type = personal,
confirmation = manual,
confirmation = Confirmation,
rating_avg = 0.0,
rating_count = 0,
status = active,

View File

@@ -37,12 +37,15 @@ start_http() ->
{"/v1/login", handler_login, []},
{"/v1/refresh", handler_refresh, []},
{"/v1/user/me", handler_user_me, []},
{"/v1/user/bookings", handler_user_bookings, []},
{"/v1/calendars", handler_calendars, []},
{"/v1/calendars/:id", handler_calendar_by_id, []},
{"/v1/calendars/:calendar_id/events", handler_events, []},
{"/v1/events/:id", handler_event_by_id, []},
{"/v1/events/:id/occurrences", handler_event_occurrences, []},
{"/v1/events/:id/occurrences/:start_time", handler_event_occurrences, []}
{"/v1/events/:id/occurrences/:start_time", handler_event_occurrences, []},
{"/v1/events/:id/bookings", handler_bookings, []},
{"/v1/bookings/:id", handler_booking_by_id, []}
]}
]),

View File

@@ -0,0 +1,128 @@
-module(handler_booking_by_id).
-include("records.hrl").
-export([init/2]).
init(Req, Opts) ->
handle(Req, Opts).
handle(Req, _Opts) ->
case cowboy_req:method(Req) of
<<"GET">> -> get_booking(Req);
<<"PUT">> -> update_booking(Req);
<<"DELETE">> -> cancel_booking(Req);
_ -> send_error(Req, 405, <<"Method not allowed">>)
end.
%% GET /v1/bookings/:id - получение бронирования
get_booking(Req) ->
case handler_auth:authenticate(Req) of
{ok, UserId, Req1} ->
BookingId = cowboy_req:binding(id, Req1),
case logic_booking:get_booking(UserId, BookingId) of
{ok, Booking} ->
Response = booking_to_json(Booking),
send_json(Req1, 200, Response);
{error, access_denied} ->
send_error(Req1, 403, <<"Access denied">>);
{error, not_found} ->
send_error(Req1, 404, <<"Booking not found">>);
{error, _} ->
send_error(Req1, 500, <<"Internal server error">>)
end;
{error, Code, Message, Req1} ->
send_error(Req1, Code, Message)
end.
%% PUT /v1/bookings/:id - подтверждение/отклонение бронирования (владельцем)
update_booking(Req) ->
case handler_auth:authenticate(Req) of
{ok, UserId, Req1} ->
BookingId = cowboy_req:binding(id, Req1),
{ok, Body, Req2} = cowboy_req:read_body(Req1),
try jsx:decode(Body, [return_maps]) of
Decoded when is_map(Decoded) ->
case maps:get(<<"action">>, Decoded, undefined) of
<<"confirm">> ->
case logic_booking:confirm_booking(UserId, BookingId, confirm) of
{ok, Booking} ->
Response = booking_to_json(Booking),
send_json(Req2, 200, Response);
{error, access_denied} ->
send_error(Req2, 403, <<"Access denied">>);
{error, not_found} ->
send_error(Req2, 404, <<"Booking not found">>);
{error, _} ->
send_error(Req2, 500, <<"Internal server error">>)
end;
<<"decline">> ->
case logic_booking:confirm_booking(UserId, BookingId, decline) of
{ok, Booking} ->
Response = booking_to_json(Booking),
send_json(Req2, 200, Response);
{error, access_denied} ->
send_error(Req2, 403, <<"Access denied">>);
{error, not_found} ->
send_error(Req2, 404, <<"Booking not found">>);
{error, _} ->
send_error(Req2, 500, <<"Internal server error">>)
end;
_ ->
send_error(Req2, 400, <<"Missing or invalid 'action' field. Use 'confirm' or 'decline'">>)
end;
_ ->
send_error(Req2, 400, <<"Invalid JSON">>)
catch
_:_ ->
send_error(Req2, 400, <<"Invalid JSON format">>)
end;
{error, Code, Message, Req1} ->
send_error(Req1, Code, Message)
end.
%% DELETE /v1/bookings/:id - отмена бронирования (участником)
cancel_booking(Req) ->
case handler_auth:authenticate(Req) of
{ok, UserId, Req1} ->
BookingId = cowboy_req:binding(id, Req1),
case logic_booking:cancel_booking(UserId, BookingId) of
{ok, Booking} ->
Response = booking_to_json(Booking),
send_json(Req1, 200, Response);
{error, access_denied} ->
send_error(Req1, 403, <<"Access denied">>);
{error, not_found} ->
send_error(Req1, 404, <<"Booking not found">>);
{error, _} ->
send_error(Req1, 500, <<"Internal server error">>)
end;
{error, Code, Message, Req1} ->
send_error(Req1, Code, Message)
end.
%% Вспомогательные функции
booking_to_json(Booking) ->
#{
id => Booking#booking.id,
event_id => Booking#booking.event_id,
user_id => Booking#booking.user_id,
status => Booking#booking.status,
confirmed_at => case Booking#booking.confirmed_at of
undefined -> null;
Dt -> datetime_to_iso8601(Dt)
end,
created_at => datetime_to_iso8601(Booking#booking.created_at),
updated_at => datetime_to_iso8601(Booking#booking.updated_at)
}.
datetime_to_iso8601({{Year, Month, Day}, {Hour, Minute, Second}}) ->
iolist_to_binary(io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0BZ",
[Year, Month, Day, Hour, Minute, Second])).
send_json(Req, Status, Data) ->
Body = jsx:encode(Data),
cowboy_req:reply(Status, #{<<"content-type">> => <<"application/json">>}, Body, Req).
send_error(Req, Status, Message) ->
Body = jsx:encode(#{error => Message}),
cowboy_req:reply(Status, #{<<"content-type">> => <<"application/json">>}, Body, Req).

View File

@@ -0,0 +1,87 @@
-module(handler_bookings).
-include("records.hrl").
-export([init/2]).
init(Req, Opts) ->
handle(Req, Opts).
handle(Req, _Opts) ->
case cowboy_req:method(Req) of
<<"POST">> -> create_booking(Req);
<<"GET">> -> list_bookings(Req);
_ -> send_error(Req, 405, <<"Method not allowed">>)
end.
%% POST /v1/events/:id/bookings - создание бронирования (запись на событие)
create_booking(Req) ->
case handler_auth:authenticate(Req) of
{ok, UserId, Req1} ->
EventId = cowboy_req:binding(id, Req1),
case logic_booking:create_booking(UserId, EventId) of
{ok, Booking} ->
Response = booking_to_json(Booking),
send_json(Req1, 201, Response);
{error, already_booked} ->
send_error(Req1, 409, <<"Already booked">>);
{error, event_full} ->
send_error(Req1, 400, <<"Event is full">>);
{error, event_not_active} ->
send_error(Req1, 400, <<"Event is not active">>);
{error, access_denied} ->
send_error(Req1, 403, <<"Access denied">>);
{error, not_found} ->
send_error(Req1, 404, <<"Event not found">>);
{error, _} ->
send_error(Req1, 500, <<"Internal server error">>)
end;
{error, Code, Message, Req1} ->
send_error(Req1, Code, Message)
end.
%% GET /v1/events/:id/bookings - список бронирований события (для владельца)
list_bookings(Req) ->
case handler_auth:authenticate(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],
send_json(Req1, 200, Response);
{error, access_denied} ->
send_error(Req1, 403, <<"Access denied">>);
{error, not_found} ->
send_error(Req1, 404, <<"Event not found">>);
{error, _} ->
send_error(Req1, 500, <<"Internal server error">>)
end;
{error, Code, Message, Req1} ->
send_error(Req1, Code, Message)
end.
%% Вспомогательные функции
booking_to_json(Booking) ->
#{
id => Booking#booking.id,
event_id => Booking#booking.event_id,
user_id => Booking#booking.user_id,
status => Booking#booking.status,
confirmed_at => case Booking#booking.confirmed_at of
undefined -> null;
Dt -> datetime_to_iso8601(Dt)
end,
created_at => datetime_to_iso8601(Booking#booking.created_at),
updated_at => datetime_to_iso8601(Booking#booking.updated_at)
}.
datetime_to_iso8601({{Year, Month, Day}, {Hour, Minute, Second}}) ->
iolist_to_binary(io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0BZ",
[Year, Month, Day, Hour, Minute, Second])).
send_json(Req, Status, Data) ->
Body = jsx:encode(Data),
cowboy_req:reply(Status, #{<<"content-type">> => <<"application/json">>}, Body, Req).
send_error(Req, Status, Message) ->
Body = jsx:encode(#{error => Message}),
cowboy_req:reply(Status, #{<<"content-type">> => <<"application/json">>}, Body, Req).

View File

@@ -23,7 +23,8 @@ create_calendar(Req) ->
case Decoded of
#{<<"title">> := Title} ->
Description = maps:get(<<"description">>, Decoded, <<"">>),
case logic_calendar:create_calendar(UserId, Title, Description) of
Confirmation = parse_confirmation(maps:get(<<"confirmation">>, Decoded, <<"manual">>)),
case logic_calendar:create_calendar(UserId, Title, Description, Confirmation) of
{ok, Calendar} ->
Response = calendar_to_json(Calendar),
send_json(Req2, 201, Response);
@@ -45,6 +46,11 @@ create_calendar(Req) ->
send_error(Req1, Code, Message)
end.
parse_confirmation(<<"auto">>) -> auto;
parse_confirmation(<<"manual">>) -> manual;
parse_confirmation(#{<<"timeout">> := N}) when is_integer(N), N > 0 -> {timeout, N};
parse_confirmation(_) -> manual.
%% GET /v1/calendars - список календарей
list_calendars(Req) ->
case handler_auth:authenticate(Req) of

View File

@@ -9,41 +9,54 @@ init(Req, Opts) ->
handle(Req, _Opts) ->
case cowboy_req:method(Req) of
<<"POST">> ->
{ok, Body, Req1} = cowboy_req:read_body(Req),
case jsx:decode(Body, [return_maps]) of
#{<<"email">> := Email, <<"password">> := Password} ->
case core_user:get_by_email(Email) of
{ok, User} ->
case logic_auth:verify_password(Password, User#user.password_hash) of
{ok, true} ->
case User#user.status of
active ->
Token = logic_auth:generate_jwt(User#user.id, User#user.role),
{RefreshToken, ExpiresAt} = logic_auth:generate_refresh_token(User#user.id),
save_refresh_token(User#user.id, RefreshToken, ExpiresAt),
Response = #{
user => #{
id => User#user.id,
email => User#user.email,
role => User#user.role
},
token => Token,
refresh_token => RefreshToken
},
send_json(Req1, 200, Response);
frozen ->
send_error(Req1, 403, <<"Account frozen">>);
deleted ->
send_error(Req1, 403, <<"Account deleted">>)
case cowboy_req:has_body(Req) of
true ->
{ok, Body, Req1} = cowboy_req:read_body(Req),
case Body of
<<>> ->
send_error(Req1, 400, <<"Empty request body">>);
_ ->
try jsx:decode(Body, [return_maps]) of
#{<<"email">> := Email, <<"password">> := Password} ->
case core_user:get_by_email(Email) of
{ok, User} ->
case logic_auth:verify_password(Password, User#user.password_hash) of
{ok, true} ->
case User#user.status of
active ->
Token = logic_auth:generate_jwt(User#user.id, User#user.role),
{RefreshToken, ExpiresAt} = logic_auth:generate_refresh_token(User#user.id),
save_refresh_token(User#user.id, RefreshToken, ExpiresAt),
Response = #{
user => #{
id => User#user.id,
email => User#user.email,
role => User#user.role
},
token => Token,
refresh_token => RefreshToken
},
send_json(Req1, 200, Response);
frozen ->
send_error(Req1, 403, <<"Account frozen">>);
deleted ->
send_error(Req1, 403, <<"Account deleted">>)
end;
_ ->
send_error(Req1, 401, <<"Invalid credentials">>)
end;
{error, not_found} ->
send_error(Req1, 401, <<"Invalid credentials">>)
end;
_ ->
send_error(Req1, 401, <<"Invalid credentials">>)
end;
{error, not_found} ->
send_error(Req1, 401, <<"Invalid credentials">>)
send_error(Req1, 400, <<"Missing email or password">>)
catch
_:_ ->
send_error(Req1, 400, <<"Invalid JSON">>)
end
end;
_ ->
send_error(Req1, 400, <<"Invalid request body">>)
false ->
send_error(Req, 400, <<"Missing request body">>)
end;
_ ->
send_error(Req, 405, <<"Method not allowed">>)

View File

@@ -9,31 +9,46 @@ init(Req, Opts) ->
handle(Req, _Opts) ->
case cowboy_req:method(Req) of
<<"POST">> ->
{ok, Body, Req1} = cowboy_req:read_body(Req),
case jsx:decode(Body, [return_maps]) of
#{<<"email">> := Email, <<"password">> := Password} ->
case core_user:email_exists(Email) of
true ->
send_error(Req1, 409, <<"Email already exists">>);
false ->
case core_user:create(Email, Password) of
{ok, User} ->
Token = logic_auth:generate_jwt(User#user.id, User#user.role),
Response = #{
user => #{
id => User#user.id,
email => User#user.email,
role => User#user.role
},
token => Token
},
send_json(Req1, 201, Response);
{error, _} ->
send_error(Req1, 500, <<"Internal server error">>)
case cowboy_req:has_body(Req) of
true ->
{ok, Body, Req1} = cowboy_req:read_body(Req),
case Body of
<<>> ->
send_error(Req1, 400, <<"Empty request body">>);
_ ->
try jsx:decode(Body, [return_maps]) of
#{<<"email">> := Email, <<"password">> := Password} ->
case core_user:email_exists(Email) of
true ->
send_error(Req1, 409, <<"Email already exists">>);
false ->
case core_user:create(Email, Password) of
{ok, User} ->
Token = logic_auth:generate_jwt(User#user.id, User#user.role),
Response = #{
user => #{
id => User#user.id,
email => User#user.email,
role => User#user.role
},
token => Token
},
send_json(Req1, 201, Response);
{error, email_exists} ->
send_error(Req1, 409, <<"Email already exists">>);
{error, _} ->
send_error(Req1, 500, <<"Internal server error">>)
end
end;
_ ->
send_error(Req1, 400, <<"Missing email or password">>)
catch
_:_ ->
send_error(Req1, 400, <<"Invalid JSON">>)
end
end;
_ ->
send_error(Req1, 400, <<"Invalid request body">>)
false ->
send_error(Req, 400, <<"Missing request body">>)
end;
_ ->
send_error(Req, 405, <<"Method not allowed">>)

View File

@@ -0,0 +1,55 @@
-module(handler_user_bookings).
-include("records.hrl").
-export([init/2]).
init(Req, Opts) ->
handle(Req, Opts).
handle(Req, _Opts) ->
case cowboy_req:method(Req) of
<<"GET">> -> list_user_bookings(Req);
_ -> send_error(Req, 405, <<"Method not allowed">>)
end.
%% GET /v1/user/bookings - список бронирований текущего пользователя
list_user_bookings(Req) ->
case handler_auth:authenticate(Req) of
{ok, UserId, Req1} ->
case logic_booking:list_user_bookings(UserId) of
{ok, Bookings} ->
Response = [booking_to_json(B) || B <- Bookings],
send_json(Req1, 200, Response);
{error, _} ->
send_error(Req1, 500, <<"Internal server error">>)
end;
{error, Code, Message, Req1} ->
send_error(Req1, Code, Message)
end.
%% Вспомогательные функции
booking_to_json(Booking) ->
#{
id => Booking#booking.id,
event_id => Booking#booking.event_id,
user_id => Booking#booking.user_id,
status => Booking#booking.status,
confirmed_at => case Booking#booking.confirmed_at of
undefined -> null;
Dt -> datetime_to_iso8601(Dt)
end,
created_at => datetime_to_iso8601(Booking#booking.created_at),
updated_at => datetime_to_iso8601(Booking#booking.updated_at)
}.
datetime_to_iso8601({{Year, Month, Day}, {Hour, Minute, Second}}) ->
iolist_to_binary(io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0BZ",
[Year, Month, Day, Hour, Minute, Second])).
send_json(Req, Status, Data) ->
Body = jsx:encode(Data),
cowboy_req:reply(Status, #{<<"content-type">> => <<"application/json">>}, Body, Req).
send_error(Req, Status, Message) ->
Body = jsx:encode(#{error => Message}),
cowboy_req:reply(Status, #{<<"content-type">> => <<"application/json">>}, Body, Req).

189
src/logic/logic_booking.erl Normal file
View File

@@ -0,0 +1,189 @@
-module(logic_booking).
-include("records.hrl").
-export([create_booking/2, confirm_booking/3, cancel_booking/2]).
-export([get_booking/2, list_event_bookings/2, list_user_bookings/1]).
-export([auto_confirm/1, check_timeout_confirmations/0]).
%% Создание бронирования (запись на событие)
create_booking(UserId, EventId) ->
% Получаем событие напрямую, без проверки доступа к календарю
case core_event:get_by_id(EventId) of
{ok, Event} ->
% Проверяем, что событие активно
case Event#event.status of
active ->
% Проверяем календарь для политики подтверждения
case core_calendar:get_by_id(Event#event.calendar_id) of
{ok, Calendar} ->
case Calendar#calendar.status of
active ->
% Проверяем, что есть места
case check_capacity(EventId, Event#event.capacity) of
{ok, _} ->
% Проверяем, не записан ли уже пользователь
case core_booking:get_by_event_and_user(EventId, UserId) of
{error, not_found} ->
ActualEventId = get_actual_event_id(Event, UserId),
case core_booking:create(ActualEventId, UserId) of
{ok, Booking} ->
handle_confirmation_policy(Booking, Event, Calendar),
{ok, Booking};
Error ->
Error
end;
{ok, _} ->
{error, already_booked}
end;
{error, full} ->
{error, event_full}
end;
_ ->
{error, calendar_not_active}
end;
_ ->
{error, calendar_not_found}
end;
_ ->
{error, event_not_active}
end;
Error ->
Error
end.
%% Подтверждение бронирования (владельцем календаря)
confirm_booking(UserId, BookingId, Action) when Action =:= confirm; Action =:= decline ->
case core_booking:get_by_id(BookingId) of
{ok, Booking} ->
% Проверяем права на событие
case logic_event:get_event(UserId, Booking#booking.event_id) of
{ok, Event} ->
% Проверяем, что пользователь может редактировать календарь
case logic_calendar:get_calendar(UserId, Event#event.calendar_id) of
{ok, Calendar} ->
case logic_calendar:can_edit(UserId, Calendar) of
true ->
case Action of
confirm ->
core_booking:update_status(BookingId, confirmed);
decline ->
core_booking:update_status(BookingId, cancelled)
end;
false ->
{error, access_denied}
end;
Error ->
Error
end;
Error ->
Error
end;
Error ->
Error
end.
%% Отмена бронирования (участником)
cancel_booking(UserId, BookingId) ->
case core_booking:get_by_id(BookingId) of
{ok, Booking} ->
% Проверяем, что это бронирование пользователя
case Booking#booking.user_id =:= UserId of
true ->
core_booking:update_status(BookingId, cancelled);
false ->
{error, access_denied}
end;
Error ->
Error
end.
%% Получение бронирования
get_booking(UserId, BookingId) ->
case core_booking:get_by_id(BookingId) of
{ok, Booking} ->
% Проверяем доступ к событию
case logic_event:get_event(UserId, Booking#booking.event_id) of
{ok, _} -> {ok, Booking};
Error -> Error
end;
Error ->
Error
end.
%% Список бронирований события (для владельца)
list_event_bookings(UserId, EventId) ->
case logic_event:get_event(UserId, EventId) of
{ok, Event} ->
% Проверяем права на календарь
case logic_calendar:get_calendar(UserId, Event#event.calendar_id) of
{ok, Calendar} ->
case logic_calendar:can_edit(UserId, Calendar) of
true ->
core_booking:list_by_event(EventId);
false ->
{error, access_denied}
end;
Error ->
Error
end;
Error ->
Error
end.
%% Список бронирований пользователя
list_user_bookings(UserId) ->
core_booking:list_by_user(UserId).
%% Автоматическое подтверждение (для политики auto)
auto_confirm(BookingId) ->
core_booking:update_status(BookingId, confirmed).
%% Проверка истечения timeout подтверждений
check_timeout_confirmations() ->
% Получаем все pending бронирования для календарей с timeout
% В реальной реализации нужно периодически вызывать эту функцию
ok.
%% Внутренние функции
check_capacity(_EventId, undefined) ->
{ok, unlimited};
check_capacity(EventId, Capacity) ->
{ok, Bookings} = core_booking:list_by_event(EventId),
ConfirmedCount = length([B || B <- Bookings, B#booking.status =:= confirmed]),
case ConfirmedCount < Capacity of
true -> {ok, Capacity - ConfirmedCount};
false -> {error, full}
end.
get_actual_event_id(Event, _UserId) ->
case Event#event.event_type of
recurring ->
% Для повторяющихся событий нужно материализовать вхождение
% Здесь предполагается, что start_time передаётся в запросе
% В полной реализации нужно получать occurrence_start из параметров
Event#event.id;
single ->
Event#event.id
end.
handle_confirmation_policy(Booking, _Event, Calendar) ->
io:format("Confirmation policy: ~p~n", [Calendar#calendar.confirmation]),
case Calendar#calendar.confirmation of
auto ->
io:format("Auto-confirming booking ~p~n", [Booking#booking.id]),
auto_confirm(Booking#booking.id);
manual ->
io:format("Manual confirmation, leaving pending~n"),
ok;
{timeout, Seconds} ->
io:format("Timeout confirmation: ~p seconds~n", [Seconds]),
spawn(fun() ->
timer:sleep(Seconds * 1000),
case core_booking:get_by_id(Booking#booking.id) of
{ok, B} when B#booking.status =:= pending ->
auto_confirm(Booking#booking.id);
_ ->
ok
end
end)
end.

View File

@@ -1,18 +1,17 @@
-module(logic_calendar).
-include("records.hrl").
-export([create_calendar/3, get_calendar/2, list_calendars/1,
-export([create_calendar/4, get_calendar/2, list_calendars/1,
update_calendar/3, delete_calendar/2]).
-export([can_access/2, can_edit/2]).
%% Создание календаря
create_calendar(UserId, Title, Description) ->
% Проверка, что пользователь может создавать календари
create_calendar(UserId, Title, Description, Confirmation) ->
case core_user:get_by_id(UserId) of
{ok, User} ->
case User#user.status of
active ->
core_calendar:create(UserId, Title, Description);
core_calendar:create(UserId, Title, Description, Confirmation);
_ ->
{error, user_inactive}
end;