%%% @author Niclas Axelsson %%% @copyright (C) 2018, Niclas Axelsson %%% @doc %%% %%% @end %%% Created : 25 Jun 2018 by Niclas Axelsson -module(nova_controller). -export([ init/2, terminate/3 ]). %%%%%%%%%%%%%%%%%%%%%%%%%%% % Public functions % %%%%%%%%%%%%%%%%%%%%%%%%%%% init(Req, State = #{secure := false}) -> dispatch(Req, State); init(Req, State = #{secure := {Mod, Func}}) -> case Mod:Func(Req) of true -> dispatch(Req, State); _ -> Req1 = cowboy_req:reply(401, Req), {ok, Req1, State} end. terminate(_Reason, _Req, _State) -> ok. %%%%%%%%%%%%%%%%%%%%%%%%%%% % Private functions % %%%%%%%%%%%%%%%%%%%%%%%%%%% dispatch(Req = #{method := ReqMethod}, State = #{mod := Mod, func := Func, method := Method}) when Method == '_' orelse Method == ReqMethod -> handle(Mod, Func, Req, State); dispatch(Req, State) -> Req1 = cowboy_req:reply(404, Req), {ok, Req1, State}. handle(Mod, Fun, Req, State) -> try Mod:Fun(Req) of RetObj -> handle1(RetObj, {Mod, Fun}, Req, State) catch Type:Exception -> logger:error("Controller failed with ~p:~p", [Type, Exception]) end. handle1(RetObj, {Mod, Fun}, Req = #{method := Method}, State) -> case RetObj of {json, JSON} -> EncodedJSON = jsone:encode(JSON, [undefined_as_null]), StatusCode = case Method of <<"POST">> -> 201; _ -> 200 end, Req1 = cowboy_req:reply(StatusCode, #{ <<"content-type">> => <<"application/json">> }, EncodedJSON, Req), {ok, Req1, State}; {json, StatusCode, Headers, JSON} -> EncodedJSON = jsone:encode(JSON, [undefined_as_null]), Req1 = cowboy_req:reply(StatusCode, maps:merge(#{<<"content-type">> => <<"application/json">>}, Headers), EncodedJSON, Req), {ok, Req1, State}; {ok, Variables} -> %% Derive the view from module ViewNameAtom = get_view_name(Mod), handle_view(ViewNameAtom, Variables, #{}, Req, State); {ok, Variables, Options} -> View = case maps:get(view, Options, undefined) of undefined -> ViewName = atom_to_list(Mod) ++ "_dtl", list_to_atom(ViewName); CustomView -> CustomView end, handle_view(View, Variables, Options, Req, State); {status, Status} when is_integer(Status) -> Req1 = cowboy_req:reply(Status, #{}, Req), {ok, Req1, State}; {status, Status, Headers} when is_integer(Status) -> Req1 = cowboy_req:reply(Status, Headers, Req), {ok, Req1, State}; {cowboy_req, CowboyReq} -> {ok, CowboyReq, State}; {extern_handler, Module, Function, Payload} -> try Module:Function(Payload, Req) of {external_handler, _, _, _} -> logger:error("Infinite loop detected"), Req1 = cowboy_req:reply(500, #{}, Req), {ok, Req1, State}; RetObject -> handle1(RetObject, {Mod, Fun}, Req, State) catch _:_ -> logger:info("External handler failed") end; Other -> logger:info("Unsupported return value from controller ~p:~p/1. Returned: ~p", [Mod, Fun, Other]), Req1 = cowboy_req:reply(500, #{}, Req), {ok, Req1, State} end. handle_view(View, Variables, Options, Req, State) -> {ok, HTML} = render_dtl(View, Variables, []), Headers = case maps:get(headers, Options, undefined) of undefined -> #{<<"content-type">> => <<"text/html">>}; UserHeaders -> UserHeaders end, StatusCode = maps:get(status_code, Options, 200), Req1 = cowboy_req:reply(StatusCode, Headers, HTML, Req), {ok, Req1, State}. render_dtl(View, Variables, Options) -> case code:is_loaded(View) of false -> %% Cast a warning since the module could not be found logger:warning("Could not render ~p cause it's not loaded.", [View]); _ -> View:render(Variables, Options) end. get_view_name(Mod) when is_atom(Mod) -> StrName = get_view_name(erlang:atom_to_list(Mod)), erlang:list_to_atom(StrName); get_view_name([$_, $c, $o, $n, $t, $r, $o, $l, $l, $e, $r]) -> "_dtl"; get_view_name([H|T]) -> [H|get_view_name(T)].