From fd15cc089b9bb04aaa5d8197a040db9480556141 Mon Sep 17 00:00:00 2001 From: miraikumiko Date: Sat, 14 Jun 2025 17:15:32 +0200 Subject: [PATCH 1/6] Rename key_gen.ex to keygen.ex --- lib/nulla/{key_gen.ex => keygen.ex} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/nulla/{key_gen.ex => keygen.ex} (100%) diff --git a/lib/nulla/key_gen.ex b/lib/nulla/keygen.ex similarity index 100% rename from lib/nulla/key_gen.ex rename to lib/nulla/keygen.ex From 49ac2bbe6f9527154868c2d5b64675947e50315d Mon Sep 17 00:00:00 2001 From: miraikumiko Date: Sat, 14 Jun 2025 18:50:54 +0200 Subject: [PATCH 2/6] Update nodeinfo --- lib/nulla/models/user.ex | 32 +++++++++++++++++-- .../controllers/nodeinfo_controller.ex | 11 +++++-- .../20250530110822_create_users.exs | 4 ++- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/lib/nulla/models/user.ex b/lib/nulla/models/user.ex index b3ef7ce..ce1ce22 100644 --- a/lib/nulla/models/user.ex +++ b/lib/nulla/models/user.ex @@ -1,6 +1,7 @@ defmodule Nulla.Models.User do use Ecto.Schema import Ecto.Changeset + import Ecto.Query alias Nulla.Repo alias Nulla.Snowflake alias Nulla.Models.User @@ -8,6 +9,7 @@ defmodule Nulla.Models.User do @primary_key {:id, :integer, autogenerate: false} schema "users" do field :username, :string + field :domain, :string field :email, :string field :password, :string field :is_moderator, :boolean, default: false @@ -15,7 +17,7 @@ defmodule Nulla.Models.User do field :bio, :string field :location, :string field :birthday, :date - field :fields, :map + field :fields, {:array, :map} field :tags, {:array, :string} field :follow_approval, :boolean, default: false field :is_bot, :boolean, default: false @@ -26,6 +28,7 @@ defmodule Nulla.Models.User do field :public_key, :string field :avatar, :string field :banner, :string + field :last_active_at, :utc_datetime has_many :user_sessions, Nulla.Models.Session has_many :notes, Nulla.Models.Note @@ -39,6 +42,7 @@ defmodule Nulla.Models.User do user |> cast(attrs, [ :username, + :domain, :email, :password, :is_moderator, @@ -55,10 +59,12 @@ defmodule Nulla.Models.User do :private_key, :public_key, :avatar, - :banner + :banner, + :last_active_at ]) |> validate_required([ :username, + :domain, :email, :password, :is_moderator, @@ -75,7 +81,8 @@ defmodule Nulla.Models.User do :private_key, :public_key, :avatar, - :banner + :banner, + :last_active_at ]) end @@ -91,4 +98,23 @@ defmodule Nulla.Models.User do 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) + + def get_total_users_count(domain) do + Repo.aggregate(from(u in User, where: u.domain == ^domain), :count, :id) + end + + def get_active_users_count(domain, days) do + cutoff = DateTime.add(DateTime.utc_now(), -days * 86400, :second) + + from(u in User, + where: u.domain == ^domain and u.last_active_at > ^cutoff + ) + |> Repo.aggregate(:count, :id) + end + + def update_last_active(user) do + user + |> Ecto.Changeset.change(last_active_at: DateTime.utc_now()) + |> Repo.update() + end end diff --git a/lib/nulla_web/controllers/nodeinfo_controller.ex b/lib/nulla_web/controllers/nodeinfo_controller.ex index 92efc88..46a3bed 100644 --- a/lib/nulla_web/controllers/nodeinfo_controller.ex +++ b/lib/nulla_web/controllers/nodeinfo_controller.ex @@ -14,11 +14,16 @@ defmodule NullaWeb.NodeinfoController do def show(conn, _params) do version = Application.spec(:nulla, :vsn) |> to_string() + instance_settings = InstanceSettings.get_instance_settings!() + domain = instance_settings.domain + total = User.get_total_users_count(domain) + month = User.get_active_users_count(domain, 30) + halfyear = User.get_active_users_count(domain, 180) users = %{ - total: 0, - month: 0, - halfyear: 0 + total: total, + month: month, + halfyear: halfyear } instance_settings = InstanceSettings.get_instance_settings!() diff --git a/priv/repo/migrations/20250530110822_create_users.exs b/priv/repo/migrations/20250530110822_create_users.exs index b597c2f..2b48cfa 100644 --- a/priv/repo/migrations/20250530110822_create_users.exs +++ b/priv/repo/migrations/20250530110822_create_users.exs @@ -5,6 +5,7 @@ defmodule Nulla.Repo.Migrations.CreateUsers do create table(:users, primary_key: false) do add :id, :bigint, primary_key: true add :username, :string, null: false, unique: true + add :domain, :string, null: false add :email, :string add :password, :string add :is_moderator, :boolean, default: false, null: false @@ -12,7 +13,7 @@ defmodule Nulla.Repo.Migrations.CreateUsers do add :bio, :text add :location, :string add :birthday, :date - add :fields, :map + add :fields, :jsonb, default: "[]", null: false add :tags, {:array, :string} add :follow_approval, :boolean, default: false, null: false add :is_bot, :boolean, default: false, null: false @@ -23,6 +24,7 @@ defmodule Nulla.Repo.Migrations.CreateUsers do add :public_key, :string, null: false add :avatar, :string add :banner, :string + add :last_active_at, :utc_datetime timestamps(type: :utc_datetime) end From 83182b9e7bfb0d7e30ce8cb2fe309f2e839427c3 Mon Sep 17 00:00:00 2001 From: miraikumiko Date: Sat, 14 Jun 2025 18:52:04 +0200 Subject: [PATCH 3/6] Remove unneeded templates --- lib/nulla_web/components/templates.ex | 22 ------------------ .../components/templates/note/edit.html.heex | 8 ------- .../components/templates/note/index.html.heex | 23 ------------------- .../components/templates/note/new.html.heex | 8 ------- .../templates/note/note_form.html.heex | 9 -------- .../components/templates/note/show.html.heex | 15 ------------ .../components/templates/user/edit.html.heex | 8 ------- .../components/templates/user/index.html.heex | 23 ------------------- .../components/templates/user/new.html.heex | 8 ------- .../templates/user/user_form.html.heex | 9 -------- 10 files changed, 133 deletions(-) delete mode 100644 lib/nulla_web/components/templates/note/edit.html.heex delete mode 100644 lib/nulla_web/components/templates/note/index.html.heex delete mode 100644 lib/nulla_web/components/templates/note/new.html.heex delete mode 100644 lib/nulla_web/components/templates/note/note_form.html.heex delete mode 100644 lib/nulla_web/components/templates/note/show.html.heex delete mode 100644 lib/nulla_web/components/templates/user/edit.html.heex delete mode 100644 lib/nulla_web/components/templates/user/index.html.heex delete mode 100644 lib/nulla_web/components/templates/user/new.html.heex delete mode 100644 lib/nulla_web/components/templates/user/user_form.html.heex diff --git a/lib/nulla_web/components/templates.ex b/lib/nulla_web/components/templates.ex index 87fc303..acec111 100644 --- a/lib/nulla_web/components/templates.ex +++ b/lib/nulla_web/components/templates.ex @@ -3,14 +3,6 @@ defmodule NullaWeb.UserHTML do embed_templates "templates/user/*" - @doc """ - Renders a user form. - """ - attr :changeset, Ecto.Changeset, required: true - attr :action, :string, required: true - - def user_form(assigns) - def format_birthdate(date) do formatted = Date.to_string(date) |> String.replace("-", "/") age = Timex.diff(Timex.today(), date, :years) @@ -84,17 +76,3 @@ defmodule NullaWeb.UserHTML do end end end - -defmodule NullaWeb.NoteHTML do - use NullaWeb, :html - - embed_templates "templates/note/*" - - @doc """ - Renders a note form. - """ - attr :changeset, Ecto.Changeset, required: true - attr :action, :string, required: true - - def note_form(assigns) -end diff --git a/lib/nulla_web/components/templates/note/edit.html.heex b/lib/nulla_web/components/templates/note/edit.html.heex deleted file mode 100644 index 3bef388..0000000 --- a/lib/nulla_web/components/templates/note/edit.html.heex +++ /dev/null @@ -1,8 +0,0 @@ -<.header> - Edit Note {@note.id} - <:subtitle>Use this form to manage note records in your database. - - -<.note_form changeset={@changeset} action={~p"/notes/#{@note}"} /> - -<.back navigate={~p"/notes"}>Back to notes diff --git a/lib/nulla_web/components/templates/note/index.html.heex b/lib/nulla_web/components/templates/note/index.html.heex deleted file mode 100644 index ffeedbc..0000000 --- a/lib/nulla_web/components/templates/note/index.html.heex +++ /dev/null @@ -1,23 +0,0 @@ -<.header> - Listing Notes - <:actions> - <.link href={~p"/notes/new"}> - <.button>New Note - - - - -<.table id="notes" rows={@notes} row_click={&JS.navigate(~p"/notes/#{&1}")}> - <:col :let={note} label="Content">{note.content} - <:action :let={note}> -
- <.link navigate={~p"/notes/#{note}"}>Show -
- <.link navigate={~p"/notes/#{note}/edit"}>Edit - - <:action :let={note}> - <.link href={~p"/notes/#{note}"} method="delete" data-confirm="Are you sure?"> - Delete - - - diff --git a/lib/nulla_web/components/templates/note/new.html.heex b/lib/nulla_web/components/templates/note/new.html.heex deleted file mode 100644 index 4cf47a4..0000000 --- a/lib/nulla_web/components/templates/note/new.html.heex +++ /dev/null @@ -1,8 +0,0 @@ -<.header> - New Note - <:subtitle>Use this form to manage note records in your database. - - -<.note_form changeset={@changeset} action={~p"/notes"} /> - -<.back navigate={~p"/notes"}>Back to notes diff --git a/lib/nulla_web/components/templates/note/note_form.html.heex b/lib/nulla_web/components/templates/note/note_form.html.heex deleted file mode 100644 index da6ac0f..0000000 --- a/lib/nulla_web/components/templates/note/note_form.html.heex +++ /dev/null @@ -1,9 +0,0 @@ -<.simple_form :let={f} for={@changeset} action={@action}> - <.error :if={@changeset.action}> - Oops, something went wrong! Please check the errors below. - - <.input field={f[:content]} type="text" label="Content" /> - <:actions> - <.button>Save Note - - diff --git a/lib/nulla_web/components/templates/note/show.html.heex b/lib/nulla_web/components/templates/note/show.html.heex deleted file mode 100644 index d7f2f70..0000000 --- a/lib/nulla_web/components/templates/note/show.html.heex +++ /dev/null @@ -1,15 +0,0 @@ -<.header> - Note {@note.id} - <:subtitle>This is a note record from your database. - <:actions> - <.link href={~p"/notes/#{@note}/edit"}> - <.button>Edit note - - - - -<.list> - <:item title="Content">{@note.content} - - -<.back navigate={~p"/notes"}>Back to notes diff --git a/lib/nulla_web/components/templates/user/edit.html.heex b/lib/nulla_web/components/templates/user/edit.html.heex deleted file mode 100644 index 2f8aa66..0000000 --- a/lib/nulla_web/components/templates/user/edit.html.heex +++ /dev/null @@ -1,8 +0,0 @@ -<.header> - Edit User {@user.id} - <:subtitle>Use this form to manage user records in your database. - - -<.user_form changeset={@changeset} action={~p"/users/#{@user}"} /> - -<.back navigate={~p"/users"}>Back to users diff --git a/lib/nulla_web/components/templates/user/index.html.heex b/lib/nulla_web/components/templates/user/index.html.heex deleted file mode 100644 index 9eca5b7..0000000 --- a/lib/nulla_web/components/templates/user/index.html.heex +++ /dev/null @@ -1,23 +0,0 @@ -<.header> - Listing Users - <:actions> - <.link href={~p"/users/new"}> - <.button>New User - - - - -<.table id="users" rows={@users} row_click={&JS.navigate(~p"/users/#{&1}")}> - <:col :let={user} label="Username">{user.username} - <:action :let={user}> -
- <.link navigate={~p"/users/#{user}"}>Show -
- <.link navigate={~p"/users/#{user}/edit"}>Edit - - <:action :let={user}> - <.link href={~p"/users/#{user}"} method="delete" data-confirm="Are you sure?"> - Delete - - - diff --git a/lib/nulla_web/components/templates/user/new.html.heex b/lib/nulla_web/components/templates/user/new.html.heex deleted file mode 100644 index 9248fb0..0000000 --- a/lib/nulla_web/components/templates/user/new.html.heex +++ /dev/null @@ -1,8 +0,0 @@ -<.header> - New User - <:subtitle>Use this form to manage user records in your database. - - -<.user_form changeset={@changeset} action={~p"/users"} /> - -<.back navigate={~p"/users"}>Back to users diff --git a/lib/nulla_web/components/templates/user/user_form.html.heex b/lib/nulla_web/components/templates/user/user_form.html.heex deleted file mode 100644 index 6871618..0000000 --- a/lib/nulla_web/components/templates/user/user_form.html.heex +++ /dev/null @@ -1,9 +0,0 @@ -<.simple_form :let={f} for={@changeset} action={@action}> - <.error :if={@changeset.action}> - Oops, something went wrong! Please check the errors below. - - <.input field={f[:username]} type="text" label="Username" /> - <:actions> - <.button>Save User - - From c0bf6f62b49db6c4835d20d9f655f5d2a073f67e Mon Sep 17 00:00:00 2001 From: miraikumiko Date: Sat, 14 Jun 2025 18:52:23 +0200 Subject: [PATCH 4/6] mix format --- config/config.exs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/config.exs b/config/config.exs index fc9357b..c3983d9 100644 --- a/config/config.exs +++ b/config/config.exs @@ -23,8 +23,7 @@ config :nulla, NullaWeb.Endpoint, live_view: [signing_salt: "jcAt5/U+"] # Snowflake configuration -config :nulla, :snowflake, - worker_id: 1 +config :nulla, :snowflake, worker_id: 1 # Configures the mailer # From b582c93bb10851da988ea2a4ddcdb69fa41c361d Mon Sep 17 00:00:00 2001 From: miraikumiko Date: Sat, 14 Jun 2025 19:25:24 +0200 Subject: [PATCH 5/6] Fix user_controller.ex --- lib/nulla_web/controllers/user_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nulla_web/controllers/user_controller.ex b/lib/nulla_web/controllers/user_controller.ex index a5467e7..d85d580 100644 --- a/lib/nulla_web/controllers/user_controller.ex +++ b/lib/nulla_web/controllers/user_controller.ex @@ -16,7 +16,7 @@ defmodule NullaWeb.UserController do if accept in ["application/activity+json", "application/ld+json"] do conn |> put_resp_content_type("application/activity+json") - |> send_resp(200, ActivityPub.user(domain, user)) + |> send_resp(200, Jason.encode!(ActivityPub.user(domain, user))) else following = Utils.count_following_by_username!(user.username) followers = Utils.count_followers_by_username!(user.username) From f05741edb5c66edc47d6c57b130748735e9d2099 Mon Sep 17 00:00:00 2001 From: miraikumiko Date: Sat, 14 Jun 2025 19:27:09 +0200 Subject: [PATCH 6/6] Add outbox --- lib/nulla/activitypub.ex | 12 ++++++++++++ lib/nulla/models/note.ex | 5 +++++ lib/nulla_web/controllers/outbox_controller.ex | 18 ++++++++++++++++++ lib/nulla_web/router.ex | 1 + 4 files changed, 36 insertions(+) create mode 100644 lib/nulla_web/controllers/outbox_controller.ex diff --git a/lib/nulla/activitypub.ex b/lib/nulla/activitypub.ex index 019aef8..ec6332e 100644 --- a/lib/nulla/activitypub.ex +++ b/lib/nulla/activitypub.ex @@ -304,4 +304,16 @@ defmodule Nulla.ActivityPub do ) ) end + + @spec outbox(String.t(), String.t(), Integer.t()) :: Jason.OrderedObject.t() + def outbox(domain, username, total) do + Jason.OrderedObject.new( + "@context": "https://www.w3.org/ns/activitystreams", + id: "https://#{domain}/@#{username}/outbox", + type: "OrderedCollection", + totalItems: total, + first: "https://#{domain}/@#{username}/outbox?page=true", + last: "https://#{domain}/@#{username}/outbox?min_id=0&page=true" + ) + end end diff --git a/lib/nulla/models/note.ex b/lib/nulla/models/note.ex index efaf328..83e1eb5 100644 --- a/lib/nulla/models/note.ex +++ b/lib/nulla/models/note.ex @@ -33,4 +33,9 @@ defmodule Nulla.Models.Note do def get_note!(id), do: Repo.get!(Note, id) def get_all_notes!(user_id), do: Repo.all(from n in Note, where: n.user_id == ^user_id) + + def get_total_notes_count(user_id) do + from(n in Note, where: n.user_id == ^user_id) + |> Repo.aggregate(:count, :id) + end end diff --git a/lib/nulla_web/controllers/outbox_controller.ex b/lib/nulla_web/controllers/outbox_controller.ex new file mode 100644 index 0000000..55d13da --- /dev/null +++ b/lib/nulla_web/controllers/outbox_controller.ex @@ -0,0 +1,18 @@ +defmodule NullaWeb.OutboxController do + use NullaWeb, :controller + alias Nulla.ActivityPub + alias Nulla.Models.User + alias Nulla.Models.Note + alias Nulla.Models.InstanceSettings + + def show(conn, %{"username" => username}) do + instance_settings = InstanceSettings.get_instance_settings!() + domain = instance_settings.domain + user = User.get_user_by_username!(username) + total = Note.get_total_notes_count(user.id) + + conn + |> put_resp_content_type("application/activity+json") + |> send_resp(200, Jason.encode!(ActivityPub.outbox(domain, username, total))) + end +end diff --git a/lib/nulla_web/router.ex b/lib/nulla_web/router.ex index ac27911..6e50c78 100644 --- a/lib/nulla_web/router.ex +++ b/lib/nulla_web/router.ex @@ -22,6 +22,7 @@ defmodule NullaWeb.Router do get "/nodeinfo/2.0", NodeinfoController, :show get "/@:username", UserController, :show + get "/@:username/outbox", OutboxController, :show get "/@:username/following", FollowController, :following get "/@:username/followers", FollowController, :followers get "/@:username/:note_id", NoteController, :show