Архивирование с быстрым подключением и серверный рендеринг для веб-клиента #15

This commit is contained in:
2026-05-04 15:19:53 +03:00
parent 83ce92afa4
commit 574d0d2e43
7 changed files with 374 additions and 18 deletions
+110
View File
@@ -0,0 +1,110 @@
-module(handler_calendar_view).
-include("records.hrl").
-export([init/2]).
init(Req, Opts) ->
CalendarId = cowboy_req:binding(calendar_id, Req),
case verify_token(Req) of
{ok, UserId} ->
case is_owner(UserId, CalendarId) of
true ->
process_view(Req, CalendarId, Opts);
false ->
cowboy_req:reply(403,
#{<<"content-type">> => <<"application/json">>},
jsx:encode(#{error => <<"Access denied">>}),
Req),
{ok, Req, Opts}
end;
{error, _Reason} ->
cowboy_req:reply(401,
#{<<"content-type">> => <<"application/json">>},
jsx:encode(#{error => <<"Unauthorized">>}),
Req),
{ok, Req, Opts}
end.
verify_token(Req) ->
case cowboy_req:header(<<"authorization">>, Req) of
undefined ->
{error, no_token};
<<"Bearer ", Token/binary>> ->
case eventhub_auth:verify_user_token(Token) of
{ok, UserId, _Role} -> {ok, UserId};
{error, _} -> {error, invalid_token}
end;
_ ->
{error, invalid_header}
end.
is_owner(UserId, CalendarId) ->
case mnesia:dirty_read({calendar, CalendarId}) of
[#calendar{owner_id = UserId}] -> true;
_ -> false
end.
process_view(Req, CalendarId, Opts) ->
Qs = cowboy_req:parse_qs(Req),
MonthBin = case lists:keyfind(<<"month">>, 1, Qs) of
{<<"month">>, Value} -> Value;
false -> undefined
end,
case MonthBin of
undefined ->
cowboy_req:reply(400,
#{<<"content-type">> => <<"application/json">>},
jsx:encode(#{error => <<"Missing 'month' parameter">>}),
Req),
{ok, Req, Opts};
_ ->
case binary:split(MonthBin, <<"-">>) of
[YearStr, MonthStr] ->
Year = binary_to_integer(YearStr),
Month = binary_to_integer(MonthStr),
Events = fetch_events(CalendarId, Year, Month),
Html = calendar_html_renderer:render_month(Year, Month, Events),
Req2 = cowboy_req:reply(200,
#{<<"content-type">> => <<"text/html">>,
<<"cache-control">> => <<"public, max-age=86400">>},
Html,
Req),
{ok, Req2, Opts};
_ ->
cowboy_req:reply(400,
#{<<"content-type">> => <<"application/json">>},
jsx:encode(#{error => <<"Invalid 'month' format. Use YYYY-MM">>}),
Req),
{ok, Req, Opts}
end
end.
fetch_events(CalendarId, Year, Month) ->
IsHot = is_hot(Year, Month),
if IsHot ->
fetch_hot_events(CalendarId, Year, Month);
true ->
fetch_archive_events(CalendarId, Year, Month)
end.
is_hot(Year, Month) ->
Current = calendar:local_time(),
Target = {{Year, Month, 1}, {0,0,0}},
calendar:datetime_to_gregorian_seconds(Current)
- calendar:datetime_to_gregorian_seconds(Target) < 30*86400.
fetch_hot_events(CalendarId, Year, Month) ->
Start = {{Year, Month, 1}, {0,0,0}},
End = {{Year, Month, calendar:last_day_of_the_month(Year, Month)}, {23,59,59}},
mnesia:dirty_select(event,
[{#event{calendar_id = CalendarId, start_time = '$1', _ = '_'},
[{'>=','$1', {const, Start}}, {'=<','$1', {const, End}}],
['$_']}]).
fetch_archive_events(CalendarId, Year, Month) ->
DayStr = io_lib:format("~4.10.0B~2.10.0B", [Year, Month]),
case archive_manager:get_archive_node(lists:flatten(DayStr)) of
{ok, Node} ->
rpc:call(Node, archive_fetcher, fetch, [CalendarId, Year, Month]);
{error, _} ->
[]
end.