From b8d35fcc671be7d4688950028db4eac47e3ef27e Mon Sep 17 00:00:00 2001 From: Uriel Fanelli Date: Sat, 10 May 2025 20:08:41 +0200 Subject: [PATCH] First code --- .gitignore | 34 +++++++++++++++ rebar.config | 4 ++ rebar.lock | 17 ++++++++ src/activitypub_server.app.src | 20 +++++++++ src/activitypub_server_app.erl | 31 ++++++++++++++ src/activitypub_server_sup.erl | 13 ++++++ src/hello_handler.erl | 13 ++++++ src/start_server.erl | 5 +++ src/user_db.erl | 35 ++++++++++++++++ src/webfinger_handler.erl | 77 ++++++++++++++++++++++++++++++++++ 10 files changed, 249 insertions(+) create mode 100644 .gitignore create mode 100644 rebar.config create mode 100644 rebar.lock create mode 100644 src/activitypub_server.app.src create mode 100644 src/activitypub_server_app.erl create mode 100644 src/activitypub_server_sup.erl create mode 100644 src/hello_handler.erl create mode 100644 src/start_server.erl create mode 100644 src/user_db.erl create mode 100644 src/webfinger_handler.erl diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b872a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# File di build e compilazione +/_build/ +*.beam +*.o +*.plt + +# Dipendenze scaricate +/deps/ + +# File di test e log +.eunit/ +logs/ +erl_crash.dump + +# File generati da strumenti +.rebar/ +.rebar3/ +.cover/ +*.d + +# File temporanei di Mnesia (database locale) +Mnesia.* + +# File temporanei di editor +*~ +*.swp +*.swo + +# File temporanei di sistema +.DS_Store +Thumbs.db + +# Escludi solo i file beam da ebin, NON l'intera cartella (serve per i .app) +ebin/*.beam diff --git a/rebar.config b/rebar.config new file mode 100644 index 0000000..d6185ee --- /dev/null +++ b/rebar.config @@ -0,0 +1,4 @@ +{deps, [ + {cowboy, "2.10.0"}, + {jsx, "3.1.0"} +]}. diff --git a/rebar.lock b/rebar.lock new file mode 100644 index 0000000..b15d609 --- /dev/null +++ b/rebar.lock @@ -0,0 +1,17 @@ +{"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">>}]} +]. diff --git a/src/activitypub_server.app.src b/src/activitypub_server.app.src new file mode 100644 index 0000000..d43a585 --- /dev/null +++ b/src/activitypub_server.app.src @@ -0,0 +1,20 @@ +{application, activitypub_server, + [ + {description, "A simple ActivityPub server in Erlang"}, + {vsn, "0.1.0"}, + {modules, [ + activitypub_server_app, + activitypub_server_sup, + hello_handler, + webfinger_handler + ]}, + {registered, []}, + {applications, [ + kernel, + stdlib, + cowboy, + jsx + ]}, + {mod, {activitypub_server_app, []}}, + {env, []} + ]}. diff --git a/src/activitypub_server_app.erl b/src/activitypub_server_app.erl new file mode 100644 index 0000000..854c43b --- /dev/null +++ b/src/activitypub_server_app.erl @@ -0,0 +1,31 @@ +%% activitypub_server_app.erl +%% Modulo principale dell'applicazione OTP + +-module(activitypub_server_app). +-behaviour(application). + +%% Esporta le funzioni richieste dal behaviour application +-export([start/2, stop/1]). + +start(_Type, _Args) -> + + %% creiamo un utente di test, solo per vedere se mnesia va + user_db:init(), + user_db:add_user(<<"admin">>, #{email => <<"admin@example.com">>}), + + Dispatch = cowboy_router:compile([ + {'_', [ + {"/", hello_handler, []}, + {"/.well-known/webfinger", webfinger_handler, []} + ]} + ]), + {ok, _} = cowboy:start_clear( + http_listener, + [{port, 8080}], + #{env => #{dispatch => Dispatch}} + ), + activitypub_server_sup:start_link(). + + +stop(_State) -> + ok. diff --git a/src/activitypub_server_sup.erl b/src/activitypub_server_sup.erl new file mode 100644 index 0000000..0e87e9e --- /dev/null +++ b/src/activitypub_server_sup.erl @@ -0,0 +1,13 @@ +-module(activitypub_server_sup). +-behaviour(supervisor). + +-export([start_link/0]). +-export([init/1]). + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + + +init([]) -> + {ok, {{one_for_one, 1, 5}, []}}. + diff --git a/src/hello_handler.erl b/src/hello_handler.erl new file mode 100644 index 0000000..0fd929e --- /dev/null +++ b/src/hello_handler.erl @@ -0,0 +1,13 @@ +-module(hello_handler). +-behaviour(cowboy_handler). + +-export([init/2]). + +init(Req, State) -> + {ok, Resp} = cowboy_req:reply( + 200, + #{<<"content-type">> => <<"text/plain">>}, + <<"Hello, world!">>, + Req + ), + {ok, Resp, State}. diff --git a/src/start_server.erl b/src/start_server.erl new file mode 100644 index 0000000..be018ff --- /dev/null +++ b/src/start_server.erl @@ -0,0 +1,5 @@ +-module(start_server). +-export([main/0]). + +main() -> + application:ensure_all_started(activitypub_server). diff --git a/src/user_db.erl b/src/user_db.erl new file mode 100644 index 0000000..57c1a61 --- /dev/null +++ b/src/user_db.erl @@ -0,0 +1,35 @@ +-module(user_db). +-export([init/0, add_user/2, get_user/1, all_users/0]). + +-record(user, {username, data}). + +init() -> + mnesia:create_schema([node()]), + mnesia:start(), + mnesia:create_table(user, [ + {attributes, record_info(fields, user)}, + {disc_copies, [node()]} + ]). + +add_user(Username, Data) -> + F = fun() -> + mnesia:write(#user{username=Username, data=Data}) + end, + mnesia:transaction(F). + +get_user(Username) -> + F = fun() -> + case mnesia:read({user, Username}) of + [User] -> {ok, User}; + [] -> not_found + end + end, + {atomic, Result} = mnesia:transaction(F), + Result. + +all_users() -> + F = fun() -> + mnesia:match_object(#user{username='_', data='_'}) + end, + {atomic, Users} = mnesia:transaction(F), + Users. diff --git a/src/webfinger_handler.erl b/src/webfinger_handler.erl new file mode 100644 index 0000000..8bb200d --- /dev/null +++ b/src/webfinger_handler.erl @@ -0,0 +1,77 @@ +%% webfinger_handler.erl +%% Handler per l'endpoint /.well-known/webfinger +-module(webfinger_handler). +-behaviour(cowboy_handler). + +%% Cowboy vuole che esportiamo la funzione init/2 +-export([init/2]). + +init(Req, State) -> + Params = cowboy_req:parse_qs(Req), + Resource = proplists:get_value(<<"resource">>, Params), + + case Resource of + undefined -> + {ok, Resp} = cowboy_req:reply( + 400, + #{<<"content-type">> => <<"application/json">>}, + <<"{\"error\": \"Missing resource parameter\"}">>, + Req + ), + {ok, Resp, State}; + _ -> + case parse_acct(Resource) of + {ok, Username, Domain} -> + %% Qui interroghiamo Mnesia! + case user_db:get_user(Username) of + {ok, _User} -> + ActorUrl = <<"https://", Domain/binary, "/users/", Username/binary>>, + WebfingerMap = #{ + <<"subject">> => <<"acct:", Username/binary, "@", Domain/binary>>, + <<"links">> => [ + #{ + <<"rel">> => <<"self">>, + <<"type">> => <<"application/activity+json">>, + <<"href">> => ActorUrl + } + ] + }, + Json = jsx:encode(WebfingerMap), + {ok, Resp} = cowboy_req:reply( + 200, + #{<<"content-type">> => <<"application/jrd+json">>}, + Json, + Req + ), + {ok, Resp, State}; + not_found -> + {ok, Resp} = cowboy_req:reply( + 404, + #{<<"content-type">> => <<"application/json">>}, + <<"{\"error\": \"User not found\"}">>, + Req + ), + {ok, Resp, State} + end; + error -> + {ok, Resp} = cowboy_req:reply( + 400, + #{<<"content-type">> => <<"application/json">>}, + <<"{\"error\": \"Invalid resource format\"}">>, + Req + ), + {ok, Resp, State} + end + end. + + +%% Funzione di utilità per estrarre username e dominio da "acct:username@domain" +parse_acct(<<"acct:", Rest/binary>>) -> + case binary:split(Rest, <<"@">>) of + [Username, Domain] when Username =/= <<>>, Domain =/= <<>> -> + {ok, Username, Domain}; + _ -> + error + end; +parse_acct(_) -> + error.