feat: discovery tops for empty GET /v1/search. Refs EventHub/EventHubBack#50
CI / test (push) Successful in 22m57s
CI / deploy-ift (push) Successful in 3m22s
CI / e2e-ift (push) Successful in 1m12s
CI / deploy-stage (push) Successful in 4m21s
CI / e2e-stage (push) Successful in 1m27s

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-20 12:17:36 +03:00
parent e94c94d1a6
commit 6d52bc3a8e
3 changed files with 96 additions and 2 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ trails() ->
#{
path => <<"/v1/search">>,
method => <<"GET">>,
description => <<"Search calendars and events">>,
description => <<"Search calendars and events. Empty query (auth only) returns discovery tops by rating; use q/tags/geo/from/to for filtered search.">>,
tags => [<<"Search">>],
parameters => [
#{name => <<"type">>, in => <<"query">>, schema => #{type => string, enum => [<<"calendar">>, <<"event">>]}, description => <<"Type of entities to search">>},
+61
View File
@@ -16,6 +16,7 @@
%% ─────────────────────────────────────────────────────────────────
-define(DEFAULT_LIMIT, 20).
-define(MAX_LIMIT, 100).
-define(DISCOVERY_FETCH, 200).
-define(EARTH_RADIUS_KM, 6371.0).
%%%-------------------------------------------------------------------
@@ -36,6 +37,14 @@
search(Type, Query, UserId, Params) ->
Limit = min(maps:get(limit, Params, ?DEFAULT_LIMIT), ?MAX_LIMIT),
Offset = maps:get(offset, Params, 0),
case is_discovery_request(Query, Params) of
true ->
discovery_search(Type, UserId, Params, Limit, Offset);
false ->
filtered_search(Type, Query, UserId, Params, Limit, Offset)
end.
filtered_search(Type, Query, UserId, Params, Limit, Offset) ->
case Type of
<<"event">> ->
{ok, Total, Events} = search_events(Query, UserId, Params, Limit, Offset),
@@ -52,6 +61,58 @@ search(Type, Query, UserId, Params) ->
}}
end.
%% Пустой search (страница «Главная»): tops из stats_tops; иначе — полный scan.
is_discovery_request(Query, Params) ->
QueryEmpty = Query =:= undefined orelse Query =:= <<>>,
QueryEmpty andalso
not maps:is_key(tags, Params) andalso
not maps:is_key(from, Params) andalso
not maps:is_key(to, Params) andalso
not maps:is_key(lat, Params) andalso
not maps:is_key(lon, Params) andalso
not maps:is_key(sort, Params).
discovery_search(Type, UserId, Params, Limit, Offset) ->
case Type of
<<"event">> ->
{ok, Total, Events} = discovery_events(UserId, Params, Limit, Offset),
{ok, Total, #{<<"events">> => Events}};
<<"calendar">> ->
{ok, Total, Calendars} = discovery_calendars(UserId, Params, Limit, Offset),
{ok, Total, #{<<"calendars">> => Calendars}};
_ ->
{ok, EventsTotal, Events} = discovery_events(UserId, Params, Limit, Offset),
{ok, CalendarsTotal, Calendars} = discovery_calendars(UserId, Params, Limit, Offset),
{ok, EventsTotal + CalendarsTotal, #{
<<"events">> => Events,
<<"calendars">> => Calendars
}}
end.
discovery_events(UserId, Params, Limit, Offset) ->
FetchN = max(Limit + Offset, ?DISCOVERY_FETCH),
Tops = core_event:get_top_events_by_rating(FetchN),
Accessible = filter_accessible_events(Tops, UserId),
case Accessible of
[] ->
search_events(undefined, UserId, Params, Limit, Offset);
Items ->
Total = length(Items),
{ok, Total, format_events(paginate(Items, Limit, Offset))}
end.
discovery_calendars(UserId, Params, Limit, Offset) ->
FetchN = max(Limit + Offset, ?DISCOVERY_FETCH),
Tops = core_calendar:get_top_calendars_by_rating(FetchN),
Accessible = filter_accessible_calendars(Tops, UserId),
case Accessible of
[] ->
search_calendars(undefined, UserId, Params, Limit, Offset);
Items ->
Total = length(Items),
{ok, Total, format_calendars(paginate(Items, Limit, Offset))}
end.
%% ============ Поиск событий ============
-spec search_events(Query :: binary() | undefined,