From 7c1fe1940dc72578c75d63b284e8876d3dabd026 Mon Sep 17 00:00:00 2001 From: Aleksey Sabilin Date: Mon, 20 Jul 2026 11:17:31 +0300 Subject: [PATCH] feat: create default personal calendar on email verification. Refs EventHub/EventHubBack#49 --- src/handlers/handler_verify.erl | 7 +++- src/logic/logic_calendar.erl | 39 +++++++++++++++++++++- test/api/users/user_verification_tests.erl | 20 +++++++++-- test/unit/logic_calendar_tests.erl | 20 +++++++++-- 4 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/handlers/handler_verify.erl b/src/handlers/handler_verify.erl index 70dbf74..7f56272 100644 --- a/src/handlers/handler_verify.erl +++ b/src/handlers/handler_verify.erl @@ -19,7 +19,12 @@ init(Req, _Opts) -> {ok, UserId} -> core_user:update(UserId, [{status, active}]), core_verification:delete_token(Token), - handler_utils:send_json(Req1, 200, #{<<"message">> => <<"Account verified">>}); + case logic_calendar:ensure_default_calendar(UserId) of + ok -> + handler_utils:send_json(Req1, 200, #{<<"message">> => <<"Account verified">>}); + {error, Reason} -> + handler_utils:send_error(Req1, 500, Reason) + end; {error, expired} -> handler_utils:send_error(Req1, 410, <<"Token expired">>); {error, not_found} -> diff --git a/src/logic/logic_calendar.erl b/src/logic/logic_calendar.erl index ed6320f..6cbd1dc 100644 --- a/src/logic/logic_calendar.erl +++ b/src/logic/logic_calendar.erl @@ -2,7 +2,7 @@ -include("records.hrl"). -export([create_calendar/3, create_calendar/4, get_calendar/2, list_calendars/1, - update_calendar/3, delete_calendar/2]). + update_calendar/3, delete_calendar/2, ensure_default_calendar/1]). -export([can_access/2, can_edit/2]). -export([admin_list_all/0, admin_get_by_id/1, admin_update/2, admin_delete/1]). @@ -50,6 +50,43 @@ create_calendar(UserId, Title, Description, Confirmation, Type) -> {error, user_not_found} end. +%% @doc Создаёт дефолтный personal-календарь после активации пользователя. +%% Идемпотентно: если у владельца уже есть active personal — ok. +-spec ensure_default_calendar(UserId :: binary()) -> ok | {error, term()}. +ensure_default_calendar(UserId) -> + case has_active_personal_calendar(UserId) of + true -> + ok; + false -> + case core_user:get_by_id(UserId) of + {ok, User} -> + Title = default_calendar_title(User), + case create_calendar(UserId, Title, <<>>, manual, personal) of + {ok, _} -> ok; + Error -> Error + end; + Error -> + Error + end + end. + +has_active_personal_calendar(UserId) -> + case core_calendar:list_by_owner(UserId) of + {ok, Calendars} -> + lists:any( + fun(#calendar{type = personal}) -> true; + (_) -> false + end, + Calendars); + _ -> + false + end. + +default_calendar_title(#user{nickname = Nick}) when is_binary(Nick), byte_size(Nick) > 0 -> + Nick; +default_calendar_title(_) -> + <<"Мой календарь">>. + %% Получение календаря с проверкой доступа get_calendar(UserId, CalendarId) -> case core_calendar:get_by_id(CalendarId) of diff --git a/test/api/users/user_verification_tests.erl b/test/api/users/user_verification_tests.erl index 261165b..53b3a67 100644 --- a/test/api/users/user_verification_tests.erl +++ b/test/api/users/user_verification_tests.erl @@ -47,11 +47,27 @@ test() -> #{<<"token">> := AuthToken} = jsx:decode(list_to_binary(LoginBody), [return_maps]), ?assert(is_binary(AuthToken)), - % 6. Повторное использование того же токена – ошибка 404 + % 6. После активации — дефолтный приватный personal-календарь + Calendars = api_test_runner:client_get(<<"/v1/calendars">>, AuthToken), + ?assertEqual(1, length(Calendars)), + [DefaultCal] = Calendars, + ?assertEqual(<<"personal">>, maps:get(<<"type">>, DefaultCal)), + ?assertEqual(<<>>, maps:get(<<"short_name">>, DefaultCal)), + ExpectedTitle = case string:split(Email, <<"@">>) of + [Local, _] when byte_size(Local) > 0 -> Local; + _ -> <<"Мой календарь">> + end, + ?assertEqual(ExpectedTitle, maps:get(<<"title">>, DefaultCal)), + + % 7. Повторное использование того же токена – ошибка 404 {ok, 404, _, _} = api_test_runner:client_request(post, <<"/v1/verify">>, <<>>, jsx:encode(#{<<"token">> => Token})), - % 7. Невалидный токен – ошибка 404 + % 7. Повторное использование того же токена – ошибка 404 + {ok, 404, _, _} = api_test_runner:client_request(post, <<"/v1/verify">>, <<>>, + jsx:encode(#{<<"token">> => Token})), + + % 8. Невалидный токен – ошибка 404 {ok, 404, _, _} = api_test_runner:client_request(post, <<"/v1/verify">>, <<>>, jsx:encode(#{<<"token">> => <<"invalid_token">>})), diff --git a/test/unit/logic_calendar_tests.erl b/test/unit/logic_calendar_tests.erl index 8fdd5c0..41389d0 100644 --- a/test/unit/logic_calendar_tests.erl +++ b/test/unit/logic_calendar_tests.erl @@ -30,7 +30,8 @@ logic_calendar_test_() -> {"List calendars test", fun test_list_calendars/0}, {"Update calendar test", fun test_update_calendar/0}, {"Delete calendar test", fun test_delete_calendar/0}, - {"Access control test", fun test_access_control/0} + {"Access control test", fun test_access_control/0}, + {"Ensure default calendar test", fun test_ensure_default_calendar/0} ]}. create_test_user() -> @@ -41,6 +42,7 @@ create_test_user() -> password_hash = <<"hash">>, role = user, status = active, + nickname = <<>>, created_at = calendar:universal_time(), updated_at = calendar:universal_time() }, @@ -121,4 +123,18 @@ test_access_control() -> {ok, Frozen} = core_calendar:update(CommercialCalendar#calendar.id, [{status, frozen}]), ?assertNot(logic_calendar:can_access(OtherId, Frozen)), - ?assertNot(logic_calendar:can_access(OwnerId, Frozen)). \ No newline at end of file + ?assertNot(logic_calendar:can_access(OwnerId, Frozen)). + +test_ensure_default_calendar() -> + UserId = create_test_user(), + ok = logic_calendar:ensure_default_calendar(UserId), + {ok, Calendars} = logic_calendar:list_calendars(UserId), + ?assertEqual(1, length(Calendars)), + Cal = hd(Calendars), + ?assertEqual(personal, Cal#calendar.type), + ?assertEqual(<<>>, Cal#calendar.short_name), + ?assertEqual(active, Cal#calendar.status), + ?assert(byte_size(Cal#calendar.title) > 0), + ok = logic_calendar:ensure_default_calendar(UserId), + {ok, Calendars2} = logic_calendar:list_calendars(UserId), + ?assertEqual(1, length(Calendars2)). \ No newline at end of file