From 385afb93088fbd55925b6d3a0fb7207d5fce6b4f Mon Sep 17 00:00:00 2001 From: miraikumiko Date: Thu, 12 Jun 2025 13:31:59 +0200 Subject: [PATCH 1/5] mix format --- lib/nulla/activitypub.ex | 54 ++++++++++++------- lib/nulla/key_gen.ex | 9 ++-- lib/nulla/utils.ex | 11 ++-- .../controllers/follow_controller.ex | 36 +++++++------ lib/nulla_web/controllers/note_controller.ex | 4 +- ...0250527054942_create_instance_settings.exs | 9 ++-- 6 files changed, 77 insertions(+), 46 deletions(-) diff --git a/lib/nulla/activitypub.ex b/lib/nulla/activitypub.ex index 081678f..e3c93f8 100644 --- a/lib/nulla/activitypub.ex +++ b/lib/nulla/activitypub.ex @@ -88,20 +88,22 @@ defmodule Nulla.ActivityPub do @spec note(String.t(), Nulla.Models.Note.t()) :: Jason.OrderedObject.t() def note(domain, note) do attachment = - case note.media_attachments do - [] -> [] - attachments -> - [ - attachment: - Enum.map(attachments, fn att -> - Jason.OrderedObject.new( - type: "Document", - mediaType: att.mime_type, - url: "https://#{domain}/files/#{att.file}" - ) - end) - ] - end + case note.media_attachments do + [] -> + [] + + attachments -> + [ + attachment: + Enum.map(attachments, fn att -> + Jason.OrderedObject.new( + type: "Document", + mediaType: att.mime_type, + url: "https://#{domain}/files/#{att.file}" + ) + end) + ] + end Jason.OrderedObject.new( "@context": [ @@ -151,8 +153,16 @@ defmodule Nulla.ActivityPub do ) end - @spec following(String.t(), Nulla.Models.User.t(), Integer.t(), List.t(), Integer.t(), Integer.t()) :: Jason.OrderedObject.t() - def following(domain, user, total, following_list, page, offset) when is_integer(page) and page > 0 do + @spec following( + String.t(), + Nulla.Models.User.t(), + Integer.t(), + List.t(), + Integer.t(), + Integer.t() + ) :: Jason.OrderedObject.t() + def following(domain, user, total, following_list, page, offset) + when is_integer(page) and page > 0 do data = [ "@context": "https://www.w3.org/ns/activitystreams", id: "https://#{domain}/@#{user.username}/following?page=#{page}", @@ -194,8 +204,16 @@ defmodule Nulla.ActivityPub do ) end - @spec followers(String.t(), Nulla.Models.User.t(), Integer.t(), List.t(), Integer.t(), Integer.t()) :: Jason.OrderedObject.t() - def followers(domain, user, total, followers_list, page, offset) when is_integer(page) and page > 0 do + @spec followers( + String.t(), + Nulla.Models.User.t(), + Integer.t(), + List.t(), + Integer.t(), + Integer.t() + ) :: Jason.OrderedObject.t() + def followers(domain, user, total, followers_list, page, offset) + when is_integer(page) and page > 0 do data = [ "@context": "https://www.w3.org/ns/activitystreams", id: "https://#{domain}/@#{user.username}/followers?page=#{page}", diff --git a/lib/nulla/key_gen.ex b/lib/nulla/key_gen.ex index 48740c8..c803920 100644 --- a/lib/nulla/key_gen.ex +++ b/lib/nulla/key_gen.ex @@ -5,11 +5,14 @@ defmodule Nulla.KeyGen do {:RSAPrivateKey, :"two-prime", n, e, _d, _p, _q, _dp, _dq, _qi, _other} = rsa_key public_key = {:RSAPublicKey, n, e} - private_entry = {:PrivateKeyInfo, :public_key.der_encode(:RSAPrivateKey, rsa_key), :not_encrypted} - public_entry = {:SubjectPublicKeyInfo, :public_key.der_encode(:RSAPublicKey, public_key), :not_encrypted} + private_entry = + {:PrivateKeyInfo, :public_key.der_encode(:RSAPrivateKey, rsa_key), :not_encrypted} + + public_entry = + {:SubjectPublicKeyInfo, :public_key.der_encode(:RSAPublicKey, public_key), :not_encrypted} private_pem = :public_key.pem_encode([private_entry]) - public_pem = :public_key.pem_encode([public_entry]) + public_pem = :public_key.pem_encode([public_entry]) {public_pem, private_pem} end diff --git a/lib/nulla/utils.ex b/lib/nulla/utils.ex index 9335dd3..80a66c3 100644 --- a/lib/nulla/utils.ex +++ b/lib/nulla/utils.ex @@ -32,15 +32,17 @@ defmodule Nulla.Utils do offset = (page - 1) * per_page query = - from [f, u] in - from(f in Follow, - join: u in User, on: u.id == f.target_id, + from( + [f, u] in from(f in Follow, + join: u in User, + on: u.id == f.target_id, where: f.user_id == ^user_id, order_by: [asc: u.inserted_at], offset: ^offset, limit: ^per_page, select: u ) + ) users = Repo.all(query) @@ -73,7 +75,8 @@ defmodule Nulla.Utils do query = from f in Follow, where: f.target_id == ^user_id, - join: u in User, on: u.id == f.user_id, + join: u in User, + on: u.id == f.user_id, order_by: [asc: u.inserted_at], offset: ^offset, limit: ^per_page, diff --git a/lib/nulla_web/controllers/follow_controller.ex b/lib/nulla_web/controllers/follow_controller.ex index ff82b6a..a53fed8 100644 --- a/lib/nulla_web/controllers/follow_controller.ex +++ b/lib/nulla_web/controllers/follow_controller.ex @@ -11,16 +11,18 @@ defmodule NullaWeb.FollowController do offset = instance_settings.offset user = User.get_user_by_username!(username) total = Utils.count_following_by_username!(user.username) + page = - case Integer.parse(page_param) do - {int, _} when int > 0 -> int - _ -> 1 - end + case Integer.parse(page_param) do + {int, _} when int > 0 -> int + _ -> 1 + end + following_list = Utils.get_following_users_by_username!(user.username, page) conn - |> put_resp_content_type("application/activity+json") - |> json(ActivityPub.following(domain, user, total, following_list, page, offset)) + |> put_resp_content_type("application/activity+json") + |> json(ActivityPub.following(domain, user, total, following_list, page, offset)) end def following(conn, %{"username" => username}) do @@ -30,8 +32,8 @@ defmodule NullaWeb.FollowController do total = Utils.count_following_by_username!(user.username) conn - |> put_resp_content_type("application/activity+json") - |> json(ActivityPub.following(domain, user, total)) + |> put_resp_content_type("application/activity+json") + |> json(ActivityPub.following(domain, user, total)) end def followers(conn, %{"username" => username, "page" => page_param}) do @@ -40,16 +42,18 @@ defmodule NullaWeb.FollowController do offset = instance_settings.offset user = User.get_user_by_username!(username) total = Utils.count_followers_by_username!(user.username) + page = - case Integer.parse(page_param) do - {int, _} when int > 0 -> int - _ -> 1 - end + case Integer.parse(page_param) do + {int, _} when int > 0 -> int + _ -> 1 + end + followers_list = Utils.get_followers_by_username!(user.username, page) conn - |> put_resp_content_type("application/activity+json") - |> json(ActivityPub.followers(domain, user, total, followers_list, page, offset)) + |> put_resp_content_type("application/activity+json") + |> json(ActivityPub.followers(domain, user, total, followers_list, page, offset)) end def followers(conn, %{"username" => username}) do @@ -59,7 +63,7 @@ defmodule NullaWeb.FollowController do total = Utils.count_followers_by_username!(user.username) conn - |> put_resp_content_type("application/activity+json") - |> json(ActivityPub.followers(domain, user, total)) + |> put_resp_content_type("application/activity+json") + |> json(ActivityPub.followers(domain, user, total)) end end diff --git a/lib/nulla_web/controllers/note_controller.ex b/lib/nulla_web/controllers/note_controller.ex index ae4516a..4ad29c9 100644 --- a/lib/nulla_web/controllers/note_controller.ex +++ b/lib/nulla_web/controllers/note_controller.ex @@ -49,10 +49,10 @@ defmodule NullaWeb.NoteController do end end - #def show(conn, %{"id" => id}) do + # def show(conn, %{"id" => id}) do # note = Notes.get_note!(id) # render(conn, :show, note: note) - #end + # end def edit(conn, %{"id" => id}) do note = Notes.get_note!(id) diff --git a/priv/repo/migrations/20250527054942_create_instance_settings.exs b/priv/repo/migrations/20250527054942_create_instance_settings.exs index 45fd5f9..3f8be91 100644 --- a/priv/repo/migrations/20250527054942_create_instance_settings.exs +++ b/priv/repo/migrations/20250527054942_create_instance_settings.exs @@ -21,11 +21,14 @@ defmodule Nulla.Repo.Migrations.CreateInstanceSettings do execute(fn -> {public_key, private_key} = Nulla.KeyGen.generate_keys() now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) + domain = - Application.get_env(:nulla, NullaWeb.Endpoint, []) - |> Keyword.get(:url, []) - |> Keyword.get(:host, "localhost") + Application.get_env(:nulla, NullaWeb.Endpoint, []) + |> Keyword.get(:url, []) + |> Keyword.get(:host, "localhost") + esc = fn str -> "'#{String.replace(str, "'", "''")}'" end + sql = """ INSERT INTO instance_settings ( name, description, domain, registration, From 7372e0fdc15da6071ba105de4ff1d303745f09eb Mon Sep 17 00:00:00 2001 From: miraikumiko Date: Thu, 12 Jun 2025 14:06:19 +0200 Subject: [PATCH 2/5] Update bio in users table --- priv/repo/migrations/20250530110822_create_users.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/priv/repo/migrations/20250530110822_create_users.exs b/priv/repo/migrations/20250530110822_create_users.exs index 54ab8cb..cc7ab33 100644 --- a/priv/repo/migrations/20250530110822_create_users.exs +++ b/priv/repo/migrations/20250530110822_create_users.exs @@ -8,7 +8,7 @@ defmodule Nulla.Repo.Migrations.CreateUsers do add :password, :string add :is_moderator, :boolean, default: false, null: false add :realname, :string - add :bio, :string + add :bio, :text add :location, :string add :birthday, :date add :fields, :map From 76ec1d144f1f11b2e2463c0bbab99877c9f30407 Mon Sep 17 00:00:00 2001 From: miraikumiko Date: Thu, 12 Jun 2025 14:13:13 +0200 Subject: [PATCH 3/5] Add favicon.ico --- priv/static/favicon.ico | Bin 152 -> 165031 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/priv/static/favicon.ico b/priv/static/favicon.ico index 7f372bfc21cdd8cb47585339d5fa4d9dd424402f..145cc2915017fdec6869d5f8c6012926e2669028 100644 GIT binary patch literal 165031 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%0AuXvAW7bO&iJBD6^#gn*TIe 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 E0ml}Q#sB~S literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=@t!V@Ar*{oFEH`~d50E!_s``s q?{G*w(7?#d#v@^nKnY_HKaYb01EZMZjMqTJ89ZJ6T-G@yGywoKK_h|y From 82c641035adaf7aa55e58178fbc770db3891ead0 Mon Sep 17 00:00:00 2001 From: miraikumiko Date: Thu, 12 Jun 2025 14:36:44 +0200 Subject: [PATCH 4/5] Format controllers --- lib/nulla_web/controllers/follow_controller.ex | 4 ++-- lib/nulla_web/controllers/note_controller.ex | 2 +- lib/nulla_web/controllers/user_controller.ex | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/nulla_web/controllers/follow_controller.ex b/lib/nulla_web/controllers/follow_controller.ex index a53fed8..6f8a76a 100644 --- a/lib/nulla_web/controllers/follow_controller.ex +++ b/lib/nulla_web/controllers/follow_controller.ex @@ -1,9 +1,9 @@ defmodule NullaWeb.FollowController do use NullaWeb, :controller - alias Nulla.Models.User - alias Nulla.Models.InstanceSettings alias Nulla.ActivityPub alias Nulla.Utils + alias Nulla.Models.User + alias Nulla.Models.InstanceSettings def following(conn, %{"username" => username, "page" => page_param}) do instance_settings = InstanceSettings.get_instance_settings!() diff --git a/lib/nulla_web/controllers/note_controller.ex b/lib/nulla_web/controllers/note_controller.ex index 4ad29c9..bff4723 100644 --- a/lib/nulla_web/controllers/note_controller.ex +++ b/lib/nulla_web/controllers/note_controller.ex @@ -1,9 +1,9 @@ defmodule NullaWeb.NoteController do use NullaWeb, :controller alias Nulla.Repo + alias Nulla.ActivityPub alias Nulla.Models.Note alias Nulla.Models.InstanceSettings - alias Nulla.ActivityPub def index(conn, _params) do notes = Notes.list_notes() diff --git a/lib/nulla_web/controllers/user_controller.ex b/lib/nulla_web/controllers/user_controller.ex index 45ebd86..a608f48 100644 --- a/lib/nulla_web/controllers/user_controller.ex +++ b/lib/nulla_web/controllers/user_controller.ex @@ -1,10 +1,10 @@ defmodule NullaWeb.UserController do use NullaWeb, :controller + alias Nulla.ActivityPub + alias Nulla.Utils alias Nulla.Models.User alias Nulla.Models.Note alias Nulla.Models.InstanceSettings - alias Nulla.ActivityPub - alias Nulla.Utils def show(conn, %{"username" => username}) do accept = List.first(get_req_header(conn, "accept")) From b63eaa34be79ecc3b469c20cc82fc5a365d9e00c Mon Sep 17 00:00:00 2001 From: miraikumiko Date: Thu, 12 Jun 2025 16:05:18 +0200 Subject: [PATCH 5/5] Add Webfinger --- lib/nulla/activitypub.ex | 13 +++++++ lib/nulla/models/user.ex | 2 ++ .../controllers/webfinger_controller.ex | 34 +++++++++++++++++++ lib/nulla_web/router.ex | 2 ++ 4 files changed, 51 insertions(+) create mode 100644 lib/nulla_web/controllers/webfinger_controller.ex diff --git a/lib/nulla/activitypub.ex b/lib/nulla/activitypub.ex index e3c93f8..2e58798 100644 --- a/lib/nulla/activitypub.ex +++ b/lib/nulla/activitypub.ex @@ -243,4 +243,17 @@ defmodule Nulla.ActivityPub do Jason.OrderedObject.new(data) end + + def webfinger(domain, username, resource) do + Jason.OrderedObject.new( + subject: resource, + links: [ + Jason.OrderedObject.new( + rel: "self", + type: "application/activity+json", + href: "https://#{domain}/@#{username}" + ) + ] + ) + end end diff --git a/lib/nulla/models/user.ex b/lib/nulla/models/user.ex index cd20f48..caf90c5 100644 --- a/lib/nulla/models/user.ex +++ b/lib/nulla/models/user.ex @@ -77,5 +77,7 @@ defmodule Nulla.Models.User do ]) end + def get_user_by_username(username), do: Repo.get_by(User, username: username) + def get_user_by_username!(username), do: Repo.get_by!(User, username: username) end diff --git a/lib/nulla_web/controllers/webfinger_controller.ex b/lib/nulla_web/controllers/webfinger_controller.ex new file mode 100644 index 0000000..ab6e2bf --- /dev/null +++ b/lib/nulla_web/controllers/webfinger_controller.ex @@ -0,0 +1,34 @@ +defmodule NullaWeb.WebfingerController do + use NullaWeb, :controller + alias Nulla.Repo + alias Nulla.ActivityPub + alias Nulla.Models.User + alias Nulla.Models.InstanceSettings + + def show(conn, %{"resource" => resource}) do + case Regex.run(~r/^acct:([^@]+)@(.+)$/, resource) do + [_, username, domain] -> + case User.get_user_by_username(username) do + nil -> + conn + |> put_status(:not_found) + |> json(%{error: "Not Found"}) + + user -> + instance_settings = InstanceSettings.get_instance_settings!() + + if domain == instance_settings.domain do + json(conn, ActivityPub.webfinger(domain, username, resource)) + else + conn + |> put_status(:not_found) + |> json(%{error: "Not Found"}) + end + end + _ -> + conn + |> put_status(:bad_request) + |> json(%{error: "Bad Request"}) + end + end +end diff --git a/lib/nulla_web/router.ex b/lib/nulla_web/router.ex index 04268ed..0ad10df 100644 --- a/lib/nulla_web/router.ex +++ b/lib/nulla_web/router.ex @@ -21,6 +21,8 @@ defmodule NullaWeb.Router do scope "/", NullaWeb do pipe_through :browser + get "/.well-known/webfinger", WebfingerController, :show + get "/@:username", UserController, :show get "/@:username/following", FollowController, :following get "/@:username/followers", FollowController, :followers