From df548a4943def16cf10fadebcbb45a007563fa99 Mon Sep 17 00:00:00 2001 From: miraikumiko Date: Wed, 18 Jun 2025 09:13:59 +0200 Subject: [PATCH] Update --- lib/nulla/activitypub.ex | 81 +++++++--------- lib/nulla/models/activity.ex | 2 +- lib/nulla/models/actor.ex | 2 +- lib/nulla/models/instance_settings.ex | 6 +- lib/nulla/models/relation.ex | 61 ++++++++++-- lib/nulla/models/user.ex | 2 +- lib/nulla/utils.ex | 94 +------------------ lib/nulla_web/components/templates.ex | 18 ++-- .../components/templates/actor/show.html.heex | 5 +- .../controllers/follow_controller.ex | 26 ++--- lib/nulla_web/controllers/inbox_controller.ex | 15 +-- ...0250527054942_create_instance_settings.exs | 4 +- 12 files changed, 137 insertions(+), 179 deletions(-) diff --git a/lib/nulla/activitypub.ex b/lib/nulla/activitypub.ex index 16183a4..838aae4 100644 --- a/lib/nulla/activitypub.ex +++ b/lib/nulla/activitypub.ex @@ -1,4 +1,9 @@ defmodule Nulla.ActivityPub do + alias Nulla.Models.Actor + alias Nulla.Models.Activity + alias Nulla.Models.Note + alias Nulla.Models.InstanceSettings + @spec context() :: list() defp context do [ @@ -25,7 +30,7 @@ defmodule Nulla.ActivityPub do ] end - @spec actor(Nulla.Models.Actor.t()) :: Jason.OrderedObject.t() + @spec actor(Actor.t()) :: Jason.OrderedObject.t() def actor(actor) do Jason.OrderedObject.new( "@context": context(), @@ -57,7 +62,7 @@ defmodule Nulla.ActivityPub do ) end - @spec note(String.t(), Nulla.Models.Note.t()) :: Jason.OrderedObject.t() + @spec note(String.t(), Note.t()) :: Jason.OrderedObject.t() def note(domain, note) do attachment = case note.media_attachments do @@ -102,7 +107,7 @@ defmodule Nulla.ActivityPub do ) end - @spec activity(String.t(), Nulla.Models.Activity.t()) :: Jason.OrderedObject.t() + @spec activity(String.t(), Activity.t()) :: Jason.OrderedObject.t() def activity(domain, activity) do Jason.OrderedObject.new( "@context": "https://www.w3.org/ns/activitystreams", @@ -113,35 +118,28 @@ defmodule Nulla.ActivityPub do ) end - @spec following(String.t(), Nulla.Models.Actor.t(), Integer.t()) :: Jason.OrderedObject.t() - def following(domain, actor, total) do + @spec following(Actor.t(), Integer.t()) :: Jason.OrderedObject.t() + def following(actor, total) do Jason.OrderedObject.new( "@context": "https://www.w3.org/ns/activitystreams", - id: "https://#{domain}/users/#{actor.preferredUsername}/following", + id: "https://#{actor.domain}/users/#{actor.preferredUsername}/following", type: "OrderedCollection", totalItems: total, - first: "https://#{domain}/users/#{actor.preferredUsername}/following?page=1" + first: "https://#{actor.domain}/users/#{actor.preferredUsername}/following?page=1" ) end - @spec following( - String.t(), - Nulla.Models.Actor.t(), - Integer.t(), - List.t(), - Integer.t(), - Integer.t() - ) :: Jason.OrderedObject.t() - def following(domain, actor, total, following_list, page, offset) - when is_integer(page) and page > 0 do + @spec following(Actor.t(), Integer.t(), List.t(), Integer.t(), Integer.t()) :: + Jason.OrderedObject.t() + def following(actor, total, following_list, page, limit) when is_integer(page) and page > 0 do data = [ "@context": "https://www.w3.org/ns/activitystreams", - id: "https://#{domain}/@#{actor.preferredUsername}/following?page=#{page}", + id: "https://#{actor.domain}/@#{actor.preferredUsername}/following?page=#{page}", type: "OrderedCollectionPage", totalItems: total, - next: "https://#{domain}/users/#{actor.preferredUsername}/following?page=#{page + 1}", - prev: "https://#{domain}/users/#{actor.preferredUsername}/following?page=#{page - 1}", - partOf: "https://#{domain}/users/#{actor.preferredUsername}/following", + next: "https://#{actor.domain}/users/#{actor.preferredUsername}/following?page=#{page + 1}", + prev: "https://#{actor.domain}/users/#{actor.preferredUsername}/following?page=#{page - 1}", + partOf: "https://#{actor.domain}/users/#{actor.preferredUsername}/following", orderedItems: following_list ] @@ -153,7 +151,7 @@ defmodule Nulla.ActivityPub do end data = - if page * offset > total do + if page * limit > total do data |> Keyword.delete(:next) |> Keyword.delete(:prev) @@ -164,35 +162,29 @@ defmodule Nulla.ActivityPub do Jason.OrderedObject.new(data) end - @spec followers(String.t(), Nulla.Models.Actor.t(), Integer.t()) :: Jason.OrderedObject.t() - def followers(domain, actor, total) do + @spec followers(Actor.t(), Integer.t()) :: Jason.OrderedObject.t() + def followers(actor, total) do Jason.OrderedObject.new( "@context": "https://www.w3.org/ns/activitystreams", - id: "https://#{domain}/users/#{actor.preferredUsername}/followers", + id: "https://#{actor.domain}/users/#{actor.preferredUsername}/followers", type: "OrderedCollection", totalItems: total, - first: "https://#{domain}/users/#{actor.preferredUsername}/followers?page=1" + first: "https://#{actor.domain}/users/#{actor.preferredUsername}/followers?page=1" ) end - @spec followers( - String.t(), - Nulla.Models.Actor.t(), - Integer.t(), - List.t(), - Integer.t(), - Integer.t() - ) :: Jason.OrderedObject.t() - def followers(domain, actor, total, followers_list, page, offset) + @spec followers(Actor.t(), Integer.t(), List.t(), Integer.t(), Integer.t()) :: + Jason.OrderedObject.t() + def followers(actor, total, followers_list, page, limit) when is_integer(page) and page > 0 do data = [ "@context": "https://www.w3.org/ns/activitystreams", - id: "https://#{domain}/users#{actor.preferredUsername}/followers?page=#{page}", + id: "https://#{actor.domain}/users#{actor.preferredUsername}/followers?page=#{page}", type: "OrderedCollectionPage", totalItems: total, - next: "https://#{domain}/users/#{actor.preferredUsername}/followers?page=#{page + 1}", - prev: "https://#{domain}/users/#{actor.preferredUsername}/followers?page=#{page - 1}", - partOf: "https://#{domain}/users/#{actor.preferredUsername}/followers", + next: "https://#{actor.domain}/users/#{actor.preferredUsername}/followers?page=#{page + 1}", + prev: "https://#{actor.domain}/users/#{actor.preferredUsername}/followers?page=#{page - 1}", + partOf: "https://#{actor.domain}/users/#{actor.preferredUsername}/followers", orderedItems: followers_list ] @@ -204,7 +196,7 @@ defmodule Nulla.ActivityPub do end data = - if page * offset > total do + if page * limit > total do data |> Keyword.delete(:next) |> Keyword.delete(:prev) @@ -215,7 +207,7 @@ defmodule Nulla.ActivityPub do Jason.OrderedObject.new(data) end - @spec webfinger(Nulla.Models.Actor.t()) :: Jason.OrderedObject.t() + @spec webfinger(Actor.t()) :: Jason.OrderedObject.t() def webfinger(actor) do Jason.OrderedObject.new( subject: "#{actor.preferredUsername}@#{actor.domain}", @@ -255,8 +247,7 @@ defmodule Nulla.ActivityPub do ) end - @spec nodeinfo(String.t(), Map.t(), Nulla.Models.InstanceSettings.t()) :: - Jason.OrderedObject.t() + @spec nodeinfo(String.t(), Map.t(), InstanceSettings.t()) :: Jason.OrderedObject.t() def nodeinfo(version, users, instance) do Jason.OrderedObject.new( version: "2.0", @@ -323,7 +314,7 @@ defmodule Nulla.ActivityPub do ) end - @spec activity_note(Nulla.Models.Note.t()) :: Jason.OrderedObject.t() + @spec activity_note(Note.t()) :: Jason.OrderedObject.t() def activity_note(note) do Jason.OrderedObject.new( id: @@ -349,7 +340,7 @@ defmodule Nulla.ActivityPub do ) end - @spec follow_accept(Nulla.Models.Activity.t()) :: Jason.OrderedObject.t() + @spec follow_accept(Activity.t()) :: Jason.OrderedObject.t() def follow_accept(activity) do Jason.OrderedObject.new( "@context": "https://www.w3.org/ns/activitystreams", diff --git a/lib/nulla/models/activity.ex b/lib/nulla/models/activity.ex index f2649bf..f752917 100644 --- a/lib/nulla/models/activity.ex +++ b/lib/nulla/models/activity.ex @@ -28,7 +28,7 @@ defmodule Nulla.Models.Activity do %__MODULE__{} |> __MODULE__.changeset(attrs) - |> Changeset.put_change(:id, id) + |> put_change(:id, id) |> Repo.insert() end end diff --git a/lib/nulla/models/actor.ex b/lib/nulla/models/actor.ex index d181c38..8db516e 100644 --- a/lib/nulla/models/actor.ex +++ b/lib/nulla/models/actor.ex @@ -106,7 +106,7 @@ defmodule Nulla.Models.Actor do %__MODULE__{} |> changeset(attrs) - |> Changeset.put_change(:id, id) + |> put_change(:id, id) |> Repo.insert() end diff --git a/lib/nulla/models/instance_settings.ex b/lib/nulla/models/instance_settings.ex index 0a2f38f..37ffe88 100644 --- a/lib/nulla/models/instance_settings.ex +++ b/lib/nulla/models/instance_settings.ex @@ -11,7 +11,7 @@ defmodule Nulla.Models.InstanceSettings do field :registration, :boolean, default: false field :max_characters, :integer, default: 5000 field :max_upload_size, :integer, default: 50 - field :api_offset, :integer, default: 100 + field :api_limit, :integer, default: 100 field :public_key, :string field :private_key, :string end @@ -26,7 +26,7 @@ defmodule Nulla.Models.InstanceSettings do :registration, :max_characters, :max_upload_size, - :api_offset, + :api_limit, :public_key, :private_key ]) @@ -37,7 +37,7 @@ defmodule Nulla.Models.InstanceSettings do :registration, :max_characters, :max_upload_size, - :api_offset, + :api_limit, :public_key, :private_key ]) diff --git a/lib/nulla/models/relation.ex b/lib/nulla/models/relation.ex index 7d05339..3799af9 100644 --- a/lib/nulla/models/relation.ex +++ b/lib/nulla/models/relation.ex @@ -1,6 +1,7 @@ defmodule Nulla.Models.Relation do use Ecto.Schema import Ecto.Changeset + import Ecto.Query alias Nulla.Repo alias Nulla.Snowflake alias Nulla.Models.Actor @@ -21,8 +22,8 @@ defmodule Nulla.Models.Relation do field :requested, :boolean, default: false field :note, :string - belongs_to :local_actor_id, Actor - belongs_to :remote_actor_id, Actor + belongs_to :local_actor, Actor + belongs_to :remote_actor, Actor timestamps() end @@ -44,19 +45,65 @@ defmodule Nulla.Models.Relation do :domain_blocking, :requested, :note, - :source_id, - :target_id + :local_actor_id, + :remote_actor_id ]) |> validate_required([:id, :local_actor_id, :remote_actor_id]) |> unique_constraint([:local_actor_id, :remote_actor_id]) end - def create_relation(attrs) do + def create_relation(attrs) do id = Snowflake.next_id() %__MODULE__{} |> __MODULE__.changeset(attrs) - |> Changeset.put_change(:id, id) + |> put_change(:id, id) |> Repo.insert() - end + end + + def count_following(local_actor_id) do + __MODULE__ + |> where([r], r.local_actor_id == ^local_actor_id and r.following == true) + |> select([r], count(r.id)) + |> Repo.one() + end + + def get_following(local_actor_id, page, limit) when is_integer(page) and page > 0 do + offset = (page - 1) * limit + + query = + from r in __MODULE__, + join: a in Actor, + on: a.id == r.remote_actor_id, + where: r.local_actor_id == ^local_actor_id and r.following == true, + order_by: [asc: a.published], + offset: ^offset, + limit: ^limit, + select: a + + Repo.all(query) + end + + def count_followers(local_actor_id) do + __MODULE__ + |> where([r], r.local_actor_id == ^local_actor_id and r.followed_by == true) + |> select([r], count(r.id)) + |> Repo.one() + end + + def get_followers(local_actor_id, page, limit) when is_integer(page) and page > 0 do + offset = (page - 1) * limit + + query = + from r in __MODULE__, + join: a in Actor, + on: a.id == r.remote_actor_id, + where: r.local_actor_id == ^local_actor_id and r.followed_by == true, + order_by: [asc: a.published], + offset: ^offset, + limit: ^limit, + select: a + + Repo.all(query) + end end diff --git a/lib/nulla/models/user.ex b/lib/nulla/models/user.ex index 9d5966d..7766afe 100644 --- a/lib/nulla/models/user.ex +++ b/lib/nulla/models/user.ex @@ -69,7 +69,7 @@ defmodule Nulla.Models.User do def update_last_active(user) do user - |> Changeset.change(last_active_at: DateTime.utc_now()) + |> change(last_active_at: DateTime.utc_now()) |> Repo.update() end end diff --git a/lib/nulla/utils.ex b/lib/nulla/utils.ex index fc788c0..4e74f08 100644 --- a/lib/nulla/utils.ex +++ b/lib/nulla/utils.ex @@ -1,102 +1,16 @@ defmodule Nulla.Utils do - import Ecto.Query - alias Nulla.Repo - alias Nulla.Models.User - alias Nulla.Models.Follow + alias Nulla.Models.Actor alias Nulla.Models.InstanceSettings - def count_following_by_username!(username) do - case Repo.get_by(User, username: username) do - nil -> - {:error, :user_not_found} - - %User{id: user_id} -> - count = - Follow - |> where([f], f.user_id == ^user_id) - |> select([f], count(f.id)) - |> Repo.one() - - count - end - end - - def get_following_users_by_username!(username, page) when is_integer(page) and page > 0 do - case Repo.get_by(User, username: username) do - nil -> - {:error, :user_not_found} - - %User{id: user_id} -> - instance_settings = InstanceSettings.get_instance_settings!() - per_page = instance_settings.api_offset - offset = (page - 1) * per_page - - query = - 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) - - users - end - end - - def count_followers_by_username!(username) do - case Repo.get_by(User, username: username) do - nil -> - 0 - - %User{id: user_id} -> - from(f in Follow, where: f.target_id == ^user_id) - |> select([f], count(f.id)) - |> Repo.one() - end - end - - def get_followers_by_username!(username, page) when is_integer(page) and page > 0 do - case Repo.get_by(User, username: username) do - nil -> - {:error, :user_not_found} - - %User{id: user_id} -> - instance_settings = InstanceSettings.get_instance_settings!() - per_page = instance_settings.api_offset - offset = (page - 1) * per_page - - query = - from f in Follow, - where: f.target_id == ^user_id, - join: u in User, - on: u.id == f.user_id, - order_by: [asc: u.inserted_at], - offset: ^offset, - limit: ^per_page, - select: u - - users = Repo.all(query) - - users - end - end - def resolve_local_actor("https://" <> _ = uri) do case URI.parse(uri).path do "/@" <> username -> instance_settings = InstanceSettings.get_instance_settings!() domain = instance_settings.domain - case User.get_user_by_username_and_domain(username, domain) do + case Actor.get_actor(username, domain) do nil -> {:error, :not_found} - user -> {:ok, user} + user -> user end _ -> @@ -110,7 +24,7 @@ defmodule Nulla.Utils do {"Accept", "application/activity+json"} ]) - case Finch.request(request, MyApp.Finch) do + case Finch.request(request, Finch) do {:ok, %Finch.Response{status: 200, body: body}} -> case Jason.decode(body) do {:ok, data} -> {:ok, data} diff --git a/lib/nulla_web/components/templates.ex b/lib/nulla_web/components/templates.ex index 817fa17..6a61d9b 100644 --- a/lib/nulla_web/components/templates.ex +++ b/lib/nulla_web/components/templates.ex @@ -18,33 +18,37 @@ defmodule NullaWeb.ActorHTML do def format_registration_date(date) do now = Date.utc_today() formatted = Date.to_string(date) |> String.replace("-", "/") - + diff_days = Date.diff(now, date) - + cond do diff_days == 0 -> "#{formatted} (today)" - + diff_days == 1 -> "#{formatted} (1 day ago)" - + diff_days < 30 -> "#{formatted} (#{diff_days} days ago)" - + diff_days < 365 -> year_diff = now.year - date.year month_diff = now.month - date.month day_correction = if now.day < date.day, do: -1, else: 0 months = year_diff * 12 + month_diff + day_correction + if months == 1 do "#{formatted} (1 month ago)" else "#{formatted} (#{months} months ago)" end - + true -> year_diff = now.year - date.year - years = if {now.month, now.day} < {date.month, date.day}, do: year_diff - 1, else: year_diff + + years = + if {now.month, now.day} < {date.month, date.day}, do: year_diff - 1, else: year_diff + if years == 1 do "#{formatted} (1 year ago)" else diff --git a/lib/nulla_web/components/templates/actor/show.html.heex b/lib/nulla_web/components/templates/actor/show.html.heex index fb5fedf..76d3f63 100644 --- a/lib/nulla_web/components/templates/actor/show.html.heex +++ b/lib/nulla_web/components/templates/actor/show.html.heex @@ -19,7 +19,8 @@