%%%-------------------------------------------------------------------
%%% @doc Обработчик документации Swagger.
%%%
%%% Раздаёт Swagger UI и спецификации OpenAPI для
%%% административного и клиентского API.
%%%
%%% GET / – индексная страница с выбором API
%%% GET /admin/ – Swagger UI для административного API
%%% GET /admin/swagger.json – OpenAPI-спецификация (admin)
%%% GET /user/ – Swagger UI для клиентского API
%%% GET /user/swagger.json – OpenAPI-спецификация (user)
%%% @end
%%%-------------------------------------------------------------------
-module(swagger_docs_handler).
-behaviour(cowboy_handler).
-export([init/2]).
%%% cowboy_handler callback
-spec init(cowboy_req:req(), any()) -> {ok, cowboy_req:req(), any()}.
init(Req, _Opts) ->
Path = cowboy_req:path(Req),
handle(Path, Req).
%%--------------------------------------------------------------------
%% Роутинг путей
%%--------------------------------------------------------------------
-spec handle(binary(), cowboy_req:req()) -> {ok, cowboy_req:req(), any()}.
handle(<<"/">>, Req) ->
serve_index(Req);
handle(<<"/admin">>, Req) ->
redirect_to_slash(<<"/admin/">>, Req);
handle(<<"/admin/">>, Req) ->
serve_ui(admin, Req);
handle(<<"/admin/swagger.json">>, Req) ->
serve_json(admin, Req);
handle(<<"/user">>, Req) ->
redirect_to_slash(<<"/user/">>, Req);
handle(<<"/user/">>, Req) ->
serve_ui(user, Req);
handle(<<"/user/swagger.json">>, Req) ->
serve_json(user, Req);
handle(_, Req) ->
cowboy_req:reply(404, #{}, <<"Not Found">>, Req),
{ok, [], []}.
%%--------------------------------------------------------------------
%% Главная страница
%%--------------------------------------------------------------------
-spec serve_index(cowboy_req:req()) -> {ok, cowboy_req:req(), any()}.
serve_index(Req) ->
Html = <<"
EventHub API Docs
EventHub API Documentation
">>,
cowboy_req:reply(200, #{<<"content-type">> => <<"text/html">>}, Html, Req),
{ok, Html, []}.
%%--------------------------------------------------------------------
%% Swagger UI
%%--------------------------------------------------------------------
-spec serve_ui(admin | user, cowboy_req:req()) -> {ok, cowboy_req:req(), any()}.
serve_ui(Api, Req) ->
{Title, SpecUrl} = case Api of
admin -> {<<"EventHub Admin API">>, <<"/admin/swagger.json">>};
user -> {<<"EventHub User API">>, <<"/user/swagger.json">>}
end,
Html = iolist_to_binary([
"", Title,
"",
"",
"",
"",
""
]),
cowboy_req:reply(200, #{<<"content-type">> => <<"text/html">>}, Html, Req),
{ok, Html, []}.
%%--------------------------------------------------------------------
%% OpenAPI JSON
%%--------------------------------------------------------------------
-spec serve_json(admin | user, cowboy_req:req()) -> {ok, cowboy_req:req(), any()}.
serve_json(Api, Req) ->
Trails = case Api of
admin -> trails:admin();
user -> trails:user()
end,
OpenApi = #{
openapi => <<"3.0.3">>,
info => #{
title => case Api of
admin -> <<"EventHub Admin API">>;
user -> <<"EventHub User API">>
end,
version => <<"1.0.0">>
},
servers => [#{url => <<"http://localhost:8445">>, description => <<"API server">>}],
paths => build_paths(Trails)
},
Json = jsx:encode(OpenApi),
cowboy_req:reply(200, #{<<"content-type">> => <<"application/json">>}, Json, Req),
{ok, Json, []}.
%%--------------------------------------------------------------------
%% Вспомогательные функции
%%--------------------------------------------------------------------
-spec build_paths([map()]) -> map().
build_paths(Trails) ->
lists:foldl(fun(Trail, Acc) ->
Path = maps:get(path, Trail, <<"/">>),
Method0 = maps:get(method, Trail, <<"get">>),
Method = string:lowercase(Method0),
TrailData = maps:without([path, method], Trail),
PathItem = #{Method => TrailData},
maps:merge_with(fun(_, V1, V2) -> maps:merge(V1, V2) end, Acc, #{Path => PathItem})
end, #{}, Trails).
-spec redirect_to_slash(binary(), cowboy_req:req()) -> {ok, cowboy_req:req(), any()}.
redirect_to_slash(Location, Req) ->
cowboy_req:reply(301, #{<<"location">> => Location}, <<>>, Req),
{ok, [], []}.