diff --git a/priv/note.json b/priv/note.json index b70b30c..944e269 100644 --- a/priv/note.json +++ b/priv/note.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "title": "ActivityPub Note", "type": "object", - "required": ["type", "id", "content"], + "required": ["type", "content"], "properties": { "@context": { "type": "string" }, "type": { "type": "string", "enum": ["Note"] }, diff --git a/rebar.config b/rebar.config index 43136a7..5900234 100644 --- a/rebar.config +++ b/rebar.config @@ -1,4 +1,5 @@ {deps, [ + {uuid_erl, "1.5.0"}, jsonlog, jesse, {cowboy, "2.10.0"}, diff --git a/src/activitypub_server_app.erl b/src/activitypub_server_app.erl index e8607f0..9b52d86 100644 --- a/src/activitypub_server_app.erl +++ b/src/activitypub_server_app.erl @@ -10,7 +10,7 @@ start(_Type, _Args) -> %% creiamo un utente di test, solo per vedere se mnesia va - log_handler:init(), %% va per primo, se no, non logga l'avvio + log_handler:start_link(), %% va per primo, se no, non logga l'avvio db_organization:setup(), db_organization:init(), users_local_check:check_ENV(), %% controlla le variabili d'ambiente diff --git a/src/index_handler.erl b/src/index_handler.erl index c31d76a..80a24fe 100644 --- a/src/index_handler.erl +++ b/src/index_handler.erl @@ -9,10 +9,13 @@ init(Req, State) -> case json_validate:validate_activity(Body) of ok -> %% Decodifica il JSON giĆ  validato - Activity = jsx:decode(Body, [return_maps]), + Activity0 = jsx:decode(Body, [return_maps]), + Host = cowboy_req:host(Req2), + Activity = ensure_id(Activity0, Host), To = maps:get(<<"to">>, Activity, []), case users_local_check:has_local_recipient(To) of true -> + log_handler:error("Delivered ~p", [Req2]), cowboy_req:reply(200, #{}, <<"Delivered to local user">>, Req2), {ok, Req2, State}; false -> @@ -24,6 +27,7 @@ init(Req, State) -> }), case InsertResult of ok -> + log_handler:error("Saved in INBOX database ~p", [Req2]), cowboy_req:reply(202, #{}, <<"Saved to global inbox">>, Req2), {ok, Req2, State}; {aborted, Reason} -> @@ -37,6 +41,7 @@ init(Req, State) -> end end; {error, malformed_json} -> + log_handler:error("Malformed JSON ricevuto da ~p", [Req2]), cowboy_req:reply(400, #{}, <<"Malformed JSON">>, Req2), {ok, Req2, State}; {error, missing_type} -> @@ -64,3 +69,23 @@ read_full_body(Req) -> {Rest, FinalReq} = read_full_body(Req2), {<>, FinalReq} end. + + + ensure_id(Activity, Host) -> + case maps:is_key(<<"id">>, Activity) of + true -> Activity; + false -> + UUID = uuid_v4(), + UUIDBin = list_to_binary(UUID), + Id = <<"https://", Host/binary, "/activity/", UUIDBin/binary>>, + maps:put(<<"id">>, Id, Activity) + end. + + uuid_v4() -> + <> = crypto:strong_rand_bytes(16), + % Imposta i bit di versione e variant secondo lo standard UUID v4 + C1 = (C band 16#0fff) bor 16#4000, + D1 = (D band 16#3fff) bor 16#8000, + lists:flatten( + io_lib:format("~8.16.0b-~4.16.0b-~4.16.0b-~4.16.0b-~12.16.0b", [A, B, C1, D1, E]) + ). \ No newline at end of file diff --git a/src/log_handler.erl b/src/log_handler.erl index 3add5dc..ad02e6a 100644 --- a/src/log_handler.erl +++ b/src/log_handler.erl @@ -1,34 +1,89 @@ -module(log_handler). --export([init/0, info/2, error/2, warning/2, debug/2, log_map/2]). +-behaviour(gen_server). -init() -> - ok = ensure_log_dir(), - lists:foreach(fun(H) -> logger:remove_handler(H) end, logger:get_handler_ids()), - logger:add_handler(file, logger_std_h, #{ - level => debug, - config => #{file => "log/erlang.json", sync_mode_qlen => 0}, - formatter => {logger_formatter, #{}} - }), - ok. +%% API +-export([start_link/0, stop/0, info/2, error/2, warning/2, debug/2, log_map/2]). +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). + +-define(LOG_FILE, "log/erlang.jsonl"). + +-record(state, {io_device}). + +%%% API + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +stop() -> + gen_server:cast(?MODULE, stop). info(Format, Args) -> - logger:info(Format, Args). + log(<<"info">>, Format, Args). error(Format, Args) -> - logger:error(Format, Args). + log(<<"error">>, Format, Args). warning(Format, Args) -> - logger:warning(Format, Args). + log(<<"warning">>, Format, Args). debug(Format, Args) -> - logger:debug(Format, Args). + log(<<"debug">>, Format, Args). -%% Logga direttamente una mappa come evento JSON log_map(Level, Map) when is_map(Map) -> - logger:log(Level, Map). + gen_server:cast(?MODULE, {log_map, Level, Map}). + +%%% Internal log helper + +log(Level, Format, Args) -> + Msg = io_lib:format(Format, Args), + MsgBin = iolist_to_binary(Msg), + Map = #{ + timestamp => erlang:system_time(millisecond), + level => Level, + message => MsgBin + }, + gen_server:cast(?MODULE, {log_map, Level, Map}). + +%%% gen_server callbacks + +init([]) -> + ok = ensure_log_dir(), + case file:open(?LOG_FILE, [append, raw]) of + {ok, IoDevice} -> + {ok, #state{io_device=IoDevice}}; + {error, Reason} -> + {stop, {file_open_failed, Reason}} + end. + +handle_cast({log_map, _Level, Map}, State = #state{io_device=IoDevice}) -> + Json = jsx:encode(Map), + file:write(IoDevice, [Json, <<"\n">>]), + {noreply, State}; + +handle_cast(stop, State = #state{io_device=IoDevice}) -> + file:close(IoDevice), + {stop, normal, State}; + +handle_cast(_, State) -> + {noreply, State}. + +handle_call(_Request, _From, State) -> + {reply, ok, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, State = #state{io_device=IoDevice}) -> + file:close(IoDevice), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%% Utility -%% Utility per assicurarsi che la directory log/ esista ensure_log_dir() -> case file:read_file_info("log") of {ok, _} -> ok;