feat: calendar follow API (POST/DELETE follow, GET following). Refs EventHub/EventHubFront#9
CI / test (push) Failing after 6m39s
CI / deploy-ift (push) Has been skipped
CI / e2e-ift (push) Has been skipped
CI / deploy-stage (push) Has been skipped
CI / e2e-stage (push) Has been skipped

This commit is contained in:
2026-07-20 22:10:48 +03:00
parent 4932f9abae
commit 59220b955e
16 changed files with 599 additions and 4 deletions
+84
View File
@@ -0,0 +1,84 @@
%%%-------------------------------------------------------------------
%%% @doc Бизнес-логика follow чужого календаря.
%%% Follow не даёт право на отзыв (см. logic_review:can_review/3).
%%% @end
%%%-------------------------------------------------------------------
-module(logic_calendar_follow).
-include("records.hrl").
-export([follow/2, unfollow/2, is_following/2, list_following_calendars/1]).
%%%-------------------------------------------------------------------
%%% @doc Follow доступного чужого календаря.
%%% @end
%%%-------------------------------------------------------------------
-spec follow(UserId :: binary(), CalendarId :: binary()) ->
{ok, #calendar_follow{}} | {error, term()}.
follow(UserId, CalendarId) ->
case core_calendar:get_by_id(CalendarId) of
{error, not_found} ->
{error, not_found};
{ok, #calendar{owner_id = UserId}} ->
{error, own_calendar};
{ok, Calendar} ->
case logic_calendar:can_access(UserId, Calendar) of
false ->
{error, access_denied};
true ->
case Calendar#calendar.status of
active ->
core_calendar_follow:follow(CalendarId, UserId);
_ ->
{error, not_found}
end
end
end.
%%%-------------------------------------------------------------------
%%% @doc Unfollow. Идемпотентно.
%%% @end
%%%-------------------------------------------------------------------
-spec unfollow(UserId :: binary(), CalendarId :: binary()) -> ok | {error, term()}.
unfollow(UserId, CalendarId) ->
case core_calendar:get_by_id(CalendarId) of
{error, not_found} ->
%% всё равно снимаем локальный follow, если был
core_calendar_follow:unfollow(CalendarId, UserId);
{ok, #calendar{owner_id = UserId}} ->
{error, own_calendar};
{ok, _} ->
core_calendar_follow:unfollow(CalendarId, UserId)
end.
%%%-------------------------------------------------------------------
%%% @doc Флаг following для текущего пользователя.
%%% @end
%%%-------------------------------------------------------------------
-spec is_following(UserId :: binary(), CalendarId :: binary()) -> boolean().
is_following(UserId, CalendarId) ->
core_calendar_follow:is_following(UserId, CalendarId).
%%%-------------------------------------------------------------------
%%% @doc Список календарей, на которые подписан пользователь (active, доступные).
%%% @end
%%%-------------------------------------------------------------------
-spec list_following_calendars(UserId :: binary()) -> {ok, [#calendar{}]}.
list_following_calendars(UserId) ->
Follows = core_calendar_follow:list_by_user(UserId),
Calendars = lists:filtermap(
fun(#calendar_follow{calendar_id = CalId}) ->
case core_calendar:get_by_id(CalId) of
{ok, #calendar{status = active} = Cal} ->
case logic_calendar:can_access(UserId, Cal) of
true -> {true, Cal};
false -> false
end;
_ ->
false
end
end,
Follows),
Sorted = lists:sort(
fun(#calendar{title = A}, #calendar{title = B}) -> A =< B end,
Calendars),
{ok, Sorted}.