Compare commits

...

5 commits

Author SHA1 Message Date
b63eaa34be
Add Webfinger 2025-06-12 16:05:18 +02:00
82c641035a
Format controllers 2025-06-12 14:36:44 +02:00
76ec1d144f
Add favicon.ico 2025-06-12 14:13:13 +02:00
7372e0fdc1
Update bio in users table 2025-06-12 14:06:19 +02:00
385afb9308
mix format 2025-06-12 13:31:59 +02:00
12 changed files with 134 additions and 52 deletions

View file

@ -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}",
@ -225,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

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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!()
@ -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

View file

@ -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()
@ -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)

View file

@ -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"))

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 B

After

Width:  |  Height:  |  Size: 161 KiB

Before After
Before After