Schema validation
parent
c6c547df51
commit
b6735ee54d
|
@ -3,13 +3,14 @@
|
|||
*.beam
|
||||
*.o
|
||||
*.plt
|
||||
*.lock
|
||||
|
||||
# Dipendenze scaricate
|
||||
/deps/
|
||||
|
||||
# File di test e log
|
||||
.eunit/
|
||||
logs/
|
||||
log/
|
||||
erl_crash.dump
|
||||
|
||||
# File generati da strumenti
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "ActivityPub Note",
|
||||
"type": "object",
|
||||
"required": ["type", "id", "content"],
|
||||
"properties": {
|
||||
"@context": { "type": "string" },
|
||||
"type": { "type": "string", "enum": ["Note"] },
|
||||
"id": { "type": "string" },
|
||||
"attributedTo": { "type": "string" },
|
||||
"content": { "type": "string" },
|
||||
"to": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
{deps, [
|
||||
jsonlog,
|
||||
jesse,
|
||||
{cowboy, "2.10.0"},
|
||||
{jsx, "3.1.0"}
|
||||
]}.
|
||||
|
|
17
rebar.lock
17
rebar.lock
|
@ -1,17 +0,0 @@
|
|||
{"1.2.0",
|
||||
[{<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.10.0">>},0},
|
||||
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.12.1">>},1},
|
||||
{<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},0},
|
||||
{<<"ranch">>,{pkg,<<"ranch">>,<<"1.8.0">>},1}]}.
|
||||
[
|
||||
{pkg_hash,[
|
||||
{<<"cowboy">>, <<"FF9FFEFF91DAE4AE270DD975642997AFE2A1179D94B1887863E43F681A203E26">>},
|
||||
{<<"cowlib">>, <<"A9FA9A625F1D2025FE6B462CB865881329B5CAFF8F1854D1CBC9F9533F00E1E1">>},
|
||||
{<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>},
|
||||
{<<"ranch">>, <<"8C7A100A139FD57F17327B6413E4167AC559FBC04CA7448E9BE9057311597A1D">>}]},
|
||||
{pkg_hash_ext,[
|
||||
{<<"cowboy">>, <<"3AFDCCB7183CC6F143CB14D3CF51FA00E53DB9EC80CDCD525482F5E99BC41D6B">>},
|
||||
{<<"cowlib">>, <<"163B73F6367A7341B33C794C4E88E7DBFE6498AC42DCD69EF44C5BC5507C8DB0">>},
|
||||
{<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>},
|
||||
{<<"ranch">>, <<"49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5">>}]}
|
||||
].
|
|
@ -10,6 +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
|
||||
db_organization:setup(),
|
||||
db_organization:init(),
|
||||
users_local_check:check_ENV(), %% controlla le variabili d'ambiente
|
||||
|
|
|
@ -26,7 +26,7 @@ remove_oldest(Tab, Fraction) ->
|
|||
lists:foreach(fun(Rec) -> mnesia:dirty_delete_object(Rec) end, ToDelete).
|
||||
|
||||
make_pattern(global_message) -> #global_message{id='_', activity='_', timestamp='_'};
|
||||
make_pattern(ap_users) -> #ap_users{id='_', name='_', email='_', created_at='_'}. %% <-- PUNTO QUI DIOCANE!
|
||||
make_pattern(ap_users) -> #ap_users{id='_', username='_', email='_', created_at='_'}. %% <-- PUNTO QUI DIOCANE!
|
||||
|
||||
get_timestamp(#global_message{timestamp=T}) -> T;
|
||||
get_timestamp(#ap_users{created_at=T}) -> T. %% <-- PUNTO SOLO ALLA FINE, DIOCANE!
|
||||
|
|
|
@ -1,40 +1,66 @@
|
|||
%% index_handler.erl
|
||||
-module(index_handler).
|
||||
-behaviour(cowboy_handler).
|
||||
-include("db_safe_insert.hrl").
|
||||
-export([init/2]).
|
||||
|
||||
init(Req, State) ->
|
||||
{ok, Body, Req2} = cowboy_req:read_body(Req),
|
||||
{Body, Req2} = read_full_body(Req),
|
||||
case json_validate:validate_activity(Body) of
|
||||
ok ->
|
||||
%% Decodifica il JSON già validato
|
||||
Activity = jsx:decode(Body, [return_maps]),
|
||||
|
||||
%% Validazione dei campi obbligatori
|
||||
case validate_activity(Activity) of
|
||||
false ->
|
||||
{ok, Resp} = cowboy_req:reply(400, #{}, <<"Invalid ActivityPub message">>, Req2),
|
||||
{ok, Resp, State};
|
||||
true ->
|
||||
To = maps:get(<<"to">>, Activity, []),
|
||||
%% Qui uso la funzione del nuovo modulo!
|
||||
case users_local_check:has_local_recipient(To) of
|
||||
true ->
|
||||
{ok, Resp} = cowboy_req:reply(200, #{}, <<"Delivered to local user">>, Req2),
|
||||
{ok, Resp, State};
|
||||
cowboy_req:reply(200, #{}, <<"Delivered to local user">>, Req2),
|
||||
{ok, Req2, State};
|
||||
false ->
|
||||
%% Salva nell'inbox globale
|
||||
timeline_db:add_message(Activity),
|
||||
{ok, Resp} = cowboy_req:reply(202, #{}, <<"Saved to global inbox">>, Req2),
|
||||
{ok, Resp, State}
|
||||
%% SAFE INSERT NELLA GLOBAL INBOX
|
||||
InsertResult = db_safe_insert:safe_insert(global_message, #global_message{
|
||||
id = make_ref(),
|
||||
activity = Activity,
|
||||
timestamp = erlang:system_time(second)
|
||||
}),
|
||||
case InsertResult of
|
||||
ok ->
|
||||
cowboy_req:reply(202, #{}, <<"Saved to global inbox">>, Req2),
|
||||
{ok, Req2, State};
|
||||
{aborted, Reason} ->
|
||||
logger:error("DB aborted: ~p", [Reason]),
|
||||
cowboy_req:reply(500, #{}, <<"Database error">>, Req2),
|
||||
{ok, Req2, State};
|
||||
Other ->
|
||||
logger:error("DB unknown error: ~p", [Other]),
|
||||
cowboy_req:reply(500, #{}, <<"Unknown DB error">>, Req2),
|
||||
{ok, Req2, State}
|
||||
end
|
||||
end;
|
||||
{error, malformed_json} ->
|
||||
cowboy_req:reply(400, #{}, <<"Malformed JSON">>, Req2),
|
||||
{ok, Req2, State};
|
||||
{error, missing_type} ->
|
||||
cowboy_req:reply(400, #{}, <<"Missing 'type' field">>, Req2),
|
||||
{ok, Req2, State};
|
||||
{error, {unsupported_type, TypeBin}} ->
|
||||
Msg = <<"Unsupported type: ", TypeBin/binary>>,
|
||||
cowboy_req:reply(400, #{}, Msg, Req2),
|
||||
{ok, Req2, State};
|
||||
{error, invalid_type_field} ->
|
||||
cowboy_req:reply(400, #{}, <<"Invalid 'type' field">>, Req2),
|
||||
{ok, Req2, State};
|
||||
{error, Errors} ->
|
||||
ErrorMsg = io_lib:format("Invalid ActivityPub message: ~p", [Errors]),
|
||||
cowboy_req:reply(400, #{}, list_to_binary(ErrorMsg), Req2),
|
||||
{ok, Req2, State}
|
||||
end.
|
||||
|
||||
|
||||
%% vediamo di controllare se ci sono i campi obbligatori minimi di ActivityPub:
|
||||
|
||||
validate_activity(Activity) when is_map(Activity) ->
|
||||
maps:is_key(<<"type">>, Activity) andalso
|
||||
maps:is_key(<<"to">>, Activity) andalso
|
||||
maps:is_key(<<"actor">>, Activity) andalso
|
||||
maps:is_key(<<"object">>, Activity) andalso
|
||||
maps:is_key(<<"content">>, Activity).
|
||||
|
||||
|
||||
%% Funzione di utilità per leggere tutto il body
|
||||
read_full_body(Req) ->
|
||||
case cowboy_req:read_body(Req) of
|
||||
{ok, Body, Req2} ->
|
||||
{Body, Req2};
|
||||
{more, Data, Req2} ->
|
||||
{Rest, FinalReq} = read_full_body(Req2),
|
||||
{<<Data/binary, Rest/binary>>, FinalReq}
|
||||
end.
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
-module(json_validate).
|
||||
-export([validate_activity/1, load_schema/1]).
|
||||
|
||||
load_schema(SchemaName) ->
|
||||
Filename = "priv/" ++ SchemaName ++ ".json",
|
||||
{ok, Bin} = file:read_file(Filename),
|
||||
jsx:decode(Bin, [return_maps]).
|
||||
|
||||
validate_activity(JsonBin) ->
|
||||
case catch jsx:decode(JsonBin, [return_maps]) of
|
||||
{'EXIT', _} ->
|
||||
{error, malformed_json};
|
||||
JsonMap when is_map(JsonMap) ->
|
||||
case maps:get(<<"type">>, JsonMap, undefined) of
|
||||
undefined ->
|
||||
{error, missing_type};
|
||||
TypeBin when is_binary(TypeBin) ->
|
||||
TypeLower = string:lowercase(binary_to_list(TypeBin)),
|
||||
case TypeLower of
|
||||
"note" ->
|
||||
Schema = load_schema("note"),
|
||||
case jesse:validate_with_schema(Schema, JsonMap) of
|
||||
{ok, _} -> ok;
|
||||
{error, Errors} -> {error, Errors}
|
||||
end;
|
||||
OtherType ->
|
||||
{error, {unsupported_type, TypeBin}}
|
||||
end;
|
||||
_ ->
|
||||
{error, invalid_type_field}
|
||||
end
|
||||
end.
|
|
@ -0,0 +1,36 @@
|
|||
-module(log_handler).
|
||||
-export([init/0, info/2, error/2, warning/2, debug/2, log_map/2]).
|
||||
|
||||
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.
|
||||
|
||||
|
||||
info(Format, Args) ->
|
||||
logger:info(Format, Args).
|
||||
|
||||
error(Format, Args) ->
|
||||
logger:error(Format, Args).
|
||||
|
||||
warning(Format, Args) ->
|
||||
logger:warning(Format, Args).
|
||||
|
||||
debug(Format, Args) ->
|
||||
logger:debug(Format, Args).
|
||||
|
||||
%% Logga direttamente una mappa come evento JSON
|
||||
log_map(Level, Map) when is_map(Map) ->
|
||||
logger:log(Level, Map).
|
||||
|
||||
%% Utility per assicurarsi che la directory log/ esista
|
||||
ensure_log_dir() ->
|
||||
case file:read_file_info("log") of
|
||||
{ok, _} -> ok;
|
||||
_ -> file:make_dir("log")
|
||||
end.
|
|
@ -5,7 +5,7 @@
|
|||
add_user(Name, Email) ->
|
||||
Id = erlang:unique_integer([monotonic, positive]),
|
||||
Timestamp = erlang:system_time(microsecond),
|
||||
Record = #ap_users{id=Id, name=Name, email=Email, created_at=Timestamp},
|
||||
Record = #ap_users{id=Id, username=Name, email=Email, created_at=Timestamp},
|
||||
db_safe_insert:safe_insert(ap_users, Record).
|
||||
|
||||
get_user(Id) ->
|
||||
|
@ -20,7 +20,7 @@ get_user(Id) ->
|
|||
|
||||
all_users() ->
|
||||
F = fun() ->
|
||||
mnesia:match_object(#ap_users{id='_', name='_', email='_', created_at='_'})
|
||||
mnesia:match_object(#ap_users{id='_', username='_', email='_', created_at='_'})
|
||||
end,
|
||||
{atomic, Users} = mnesia:transaction(F),
|
||||
Users.
|
||||
|
|
|
@ -12,9 +12,10 @@
|
|||
check_ENV() ->
|
||||
case os:getenv("AP_FQDN") of
|
||||
false ->
|
||||
io:format("Errore: la variabile di ambiente AP_FQDN non è settata!~n"),
|
||||
log_handler:error("Errore: la variabile di ambiente AP_FQDN non è settata!~n"),
|
||||
erlang:halt(1);
|
||||
_ ->
|
||||
log_handler:debug("La variabile AP_FQDN esiste, bene", []),
|
||||
ok
|
||||
end.
|
||||
|
||||
|
@ -24,10 +25,10 @@ get_fqdn() ->
|
|||
logger:notice("Chiamata get_fqdn()~n"),
|
||||
case os:getenv("AP_FQDN") of
|
||||
false ->
|
||||
logger:notice("Errore: la variabile di ambiente AP_FQDN deve essere settata!~n"),
|
||||
log_handler:error("Errore: la variabile di ambiente AP_FQDN deve essere settata!~n"),
|
||||
exit({error, ap_fqdn_not_set});
|
||||
FQDN ->
|
||||
logger:notice("FQDN settata correttamente: ~p", [FQDN]),
|
||||
log_handler:debug("FQDN settata correttamente: ~p", [FQDN]),
|
||||
list_to_binary(FQDN)
|
||||
end.
|
||||
|
||||
|
|
Loading…
Reference in New Issue