Update
This commit is contained in:
parent
58049c93d4
commit
894866ca03
22 changed files with 344 additions and 213 deletions
|
@ -25,63 +25,35 @@ defmodule Nulla.ActivityPub do
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec user(String.t(), Nulla.Models.User.t()) :: Jason.OrderedObject.t()
|
@spec actor(Nulla.Models.Actor.t()) :: Jason.OrderedObject.t()
|
||||||
def user(domain, user) do
|
def actor(actor) do
|
||||||
Jason.OrderedObject.new(
|
Jason.OrderedObject.new(
|
||||||
"@context": context(),
|
"@context": context(),
|
||||||
id: "https://#{domain}/@#{user.username}",
|
id: actor.ap_id,
|
||||||
type: "Person",
|
type: actor.type,
|
||||||
following: "https://#{domain}/@#{user.username}/following",
|
following: actor.following,
|
||||||
followers: "https://#{domain}/@#{user.username}/followers",
|
followers: actor.followers,
|
||||||
inbox: "https://#{domain}/@#{user.username}/inbox",
|
inbox: actor.inbox,
|
||||||
outbox: "https://#{domain}/@#{user.username}/outbox",
|
outbox: actor.outbox,
|
||||||
featured: "https://#{domain}/@#{user.username}/collections/featured",
|
featured: actor.featured,
|
||||||
preferredUsername: user.username,
|
featuredTags: actor.featuredTags,
|
||||||
name: user.realname,
|
preferredUsername: actor.preferredUsername,
|
||||||
summary: user.bio,
|
name: actor.name,
|
||||||
url: "https://#{domain}/@#{user.username}",
|
summary: actor.summary,
|
||||||
manuallyApprovesFollowers: user.follow_approval,
|
url: actor.url,
|
||||||
discoverable: user.is_discoverable,
|
manuallyApprovesFollowers: actor.manuallyApprovesFollowers,
|
||||||
indexable: user.is_indexable,
|
discoverable: actor.discoverable,
|
||||||
published: DateTime.to_iso8601(user.inserted_at),
|
indexable: actor.indexable,
|
||||||
memorial: user.is_memorial,
|
published: DateTime.to_iso8601(actor.published),
|
||||||
publicKey:
|
memorial: actor.memorial,
|
||||||
Jason.OrderedObject.new(
|
publicKey: actor.publicKey,
|
||||||
id: "https://#{domain}/@#{user.username}#main-key",
|
tag: actor.tag,
|
||||||
owner: "https://#{domain}/@#{user.username}",
|
attachment: actor.attachment,
|
||||||
publicKeyPem: user.public_key
|
endpoints: actor.endpoints,
|
||||||
),
|
icon: actor.icon,
|
||||||
tag:
|
image: actor.image,
|
||||||
Enum.map(user.tags, fn tag ->
|
"vcard:bday": actor.vcard_bday,
|
||||||
Jason.OrderedObject.new(
|
"vcard:Address": actor.vcard_Address
|
||||||
type: "Hashtag",
|
|
||||||
href: "https://#{domain}/tags/#{tag}",
|
|
||||||
name: "##{tag}"
|
|
||||||
)
|
|
||||||
end),
|
|
||||||
attachment:
|
|
||||||
Enum.map(user.fields, fn {name, value} ->
|
|
||||||
Jason.OrderedObject.new(
|
|
||||||
type: "PropertyValue",
|
|
||||||
name: name,
|
|
||||||
value: value
|
|
||||||
)
|
|
||||||
end),
|
|
||||||
endpoints: Jason.OrderedObject.new(sharedInbox: "https://#{domain}/inbox"),
|
|
||||||
icon:
|
|
||||||
Jason.OrderedObject.new(
|
|
||||||
type: "Image",
|
|
||||||
mediaType: MIME.from_path(user.avatar),
|
|
||||||
url: "https://#{domain}/files/#{user.avatar}"
|
|
||||||
),
|
|
||||||
image:
|
|
||||||
Jason.OrderedObject.new(
|
|
||||||
type: "Image",
|
|
||||||
mediaType: MIME.from_path(user.banner),
|
|
||||||
url: "https://#{domain}/files/#{user.banner}"
|
|
||||||
),
|
|
||||||
"vcard:bday": user.birthday,
|
|
||||||
"vcard:Address": user.location
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -110,18 +82,18 @@ defmodule Nulla.ActivityPub do
|
||||||
"https://www.w3.org/ns/activitystreams",
|
"https://www.w3.org/ns/activitystreams",
|
||||||
Jason.OrderedObject.new(sensitive: "as:sensitive")
|
Jason.OrderedObject.new(sensitive: "as:sensitive")
|
||||||
],
|
],
|
||||||
id: "https://#{domain}/@#{note.user.username}/#{note.id}",
|
id: "https://#{domain}/users/#{note.actor.preferredUsername}/statuses/#{note.id}",
|
||||||
type: "Note",
|
type: "Note",
|
||||||
summary: nil,
|
summary: nil,
|
||||||
inReplyTo: nil,
|
inReplyTo: nil,
|
||||||
published: note.inserted_at,
|
published: note.inserted_at,
|
||||||
url: "https://#{domain}/@#{note.user.username}/#{note.id}",
|
url: "https://#{domain}/@#{note.actor.preferredUsername}/#{note.id}",
|
||||||
attributedTo: "https://#{domain}/@#{note.user.username}",
|
attributedTo: "https://#{domain}/users/#{note.actor.preferredUsername}",
|
||||||
to: [
|
to: [
|
||||||
"https://www.w3.org/ns/activitystreams#Public"
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
],
|
],
|
||||||
cc: [
|
cc: [
|
||||||
"https://#{domain}/@#{note.user.username}/followers"
|
"https://#{domain}/users/#{note.actor.preferredUsername}/followers"
|
||||||
],
|
],
|
||||||
sensetive: false,
|
sensetive: false,
|
||||||
content: note.content,
|
content: note.content,
|
||||||
|
@ -141,35 +113,35 @@ defmodule Nulla.ActivityPub do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec following(String.t(), Nulla.Models.User.t(), Integer.t()) :: Jason.OrderedObject.t()
|
@spec following(String.t(), Nulla.Models.Actor.t(), Integer.t()) :: Jason.OrderedObject.t()
|
||||||
def following(domain, user, total) do
|
def following(domain, actor, total) do
|
||||||
Jason.OrderedObject.new(
|
Jason.OrderedObject.new(
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
id: "https://#{domain}/@#{user.username}/following",
|
id: "https://#{domain}/users/#{actor.preferredUsername}/following",
|
||||||
type: "OrderedCollection",
|
type: "OrderedCollection",
|
||||||
totalItems: total,
|
totalItems: total,
|
||||||
first: "https://#{domain}/@#{user.username}/following?page=1"
|
first: "https://#{domain}/users/#{actor.preferredUsername}/following?page=1"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec following(
|
@spec following(
|
||||||
String.t(),
|
String.t(),
|
||||||
Nulla.Models.User.t(),
|
Nulla.Models.Actor.t(),
|
||||||
Integer.t(),
|
Integer.t(),
|
||||||
List.t(),
|
List.t(),
|
||||||
Integer.t(),
|
Integer.t(),
|
||||||
Integer.t()
|
Integer.t()
|
||||||
) :: Jason.OrderedObject.t()
|
) :: Jason.OrderedObject.t()
|
||||||
def following(domain, user, total, following_list, page, offset)
|
def following(domain, actor, total, following_list, page, offset)
|
||||||
when is_integer(page) and page > 0 do
|
when is_integer(page) and page > 0 do
|
||||||
data = [
|
data = [
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
id: "https://#{domain}/@#{user.username}/following?page=#{page}",
|
id: "https://#{domain}/@#{actor.preferredUsername}/following?page=#{page}",
|
||||||
type: "OrderedCollectionPage",
|
type: "OrderedCollectionPage",
|
||||||
totalItems: total,
|
totalItems: total,
|
||||||
next: "https://#{domain}/@#{user.username}/following?page=#{page + 1}",
|
next: "https://#{domain}/users/#{actor.preferredUsername}/following?page=#{page + 1}",
|
||||||
prev: "https://#{domain}/@#{user.username}/following?page=#{page - 1}",
|
prev: "https://#{domain}/users/#{actor.preferredUsername}/following?page=#{page - 1}",
|
||||||
partOf: "https://#{domain}/@#{user.username}/following",
|
partOf: "https://#{domain}/users/#{actor.preferredUsername}/following",
|
||||||
orderedItems: following_list
|
orderedItems: following_list
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -192,35 +164,35 @@ defmodule Nulla.ActivityPub do
|
||||||
Jason.OrderedObject.new(data)
|
Jason.OrderedObject.new(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec followers(String.t(), Nulla.Models.User.t(), Integer.t()) :: Jason.OrderedObject.t()
|
@spec followers(String.t(), Nulla.Models.Actor.t(), Integer.t()) :: Jason.OrderedObject.t()
|
||||||
def followers(domain, user, total) do
|
def followers(domain, actor, total) do
|
||||||
Jason.OrderedObject.new(
|
Jason.OrderedObject.new(
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
id: "https://#{domain}/@#{user.username}/followers",
|
id: "https://#{domain}/users/#{actor.preferredUsername}/followers",
|
||||||
type: "OrderedCollection",
|
type: "OrderedCollection",
|
||||||
totalItems: total,
|
totalItems: total,
|
||||||
first: "https://#{domain}/@#{user.username}/followers?page=1"
|
first: "https://#{domain}/users/#{actor.preferredUsername}/followers?page=1"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec followers(
|
@spec followers(
|
||||||
String.t(),
|
String.t(),
|
||||||
Nulla.Models.User.t(),
|
Nulla.Models.Actor.t(),
|
||||||
Integer.t(),
|
Integer.t(),
|
||||||
List.t(),
|
List.t(),
|
||||||
Integer.t(),
|
Integer.t(),
|
||||||
Integer.t()
|
Integer.t()
|
||||||
) :: Jason.OrderedObject.t()
|
) :: Jason.OrderedObject.t()
|
||||||
def followers(domain, user, total, followers_list, page, offset)
|
def followers(domain, actor, total, followers_list, page, offset)
|
||||||
when is_integer(page) and page > 0 do
|
when is_integer(page) and page > 0 do
|
||||||
data = [
|
data = [
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
id: "https://#{domain}/@#{user.username}/followers?page=#{page}",
|
id: "https://#{domain}/users#{actor.preferredUsername}/followers?page=#{page}",
|
||||||
type: "OrderedCollectionPage",
|
type: "OrderedCollectionPage",
|
||||||
totalItems: total,
|
totalItems: total,
|
||||||
next: "https://#{domain}/@#{user.username}/followers?page=#{page + 1}",
|
next: "https://#{domain}/users/#{actor.preferredUsername}/followers?page=#{page + 1}",
|
||||||
prev: "https://#{domain}/@#{user.username}/followers?page=#{page - 1}",
|
prev: "https://#{domain}/users/#{actor.preferredUsername}/followers?page=#{page - 1}",
|
||||||
partOf: "https://#{domain}/@#{user.username}/followers",
|
partOf: "https://#{domain}/users/#{actor.preferredUsername}/followers",
|
||||||
orderedItems: followers_list
|
orderedItems: followers_list
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -243,15 +215,29 @@ defmodule Nulla.ActivityPub do
|
||||||
Jason.OrderedObject.new(data)
|
Jason.OrderedObject.new(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec webfinger(String.t(), String.t(), String.t()) :: Jason.OrderedObject.t()
|
@spec webfinger(Nulla.Models.Actor.t()) :: Jason.OrderedObject.t()
|
||||||
def webfinger(domain, username, resource) do
|
def webfinger(actor) do
|
||||||
Jason.OrderedObject.new(
|
Jason.OrderedObject.new(
|
||||||
subject: resource,
|
subject: "#{actor.preferredUsername}@#{actor.domain}",
|
||||||
|
aliases: [
|
||||||
|
"https://#{actor.domain}/@#{actor.preferredUsername}",
|
||||||
|
"https://#{actor.domain}/users/#{actor.preferredUsername}"
|
||||||
|
],
|
||||||
links: [
|
links: [
|
||||||
|
Jason.OrderedObject.new(
|
||||||
|
rel: "http://webfinger.net/rel/profile-page",
|
||||||
|
type: "text/html",
|
||||||
|
href: "https://#{actor.domain}/users/#{actor.preferredUsername}"
|
||||||
|
),
|
||||||
Jason.OrderedObject.new(
|
Jason.OrderedObject.new(
|
||||||
rel: "self",
|
rel: "self",
|
||||||
type: "application/activity+json",
|
type: "application/activity+json",
|
||||||
href: "https://#{domain}/@#{username}"
|
href: "https://#{actor.domain}/users/#{actor.preferredUsername}"
|
||||||
|
),
|
||||||
|
Jason.OrderedObject.new(
|
||||||
|
rel: "http://webfinger.net/rel/avatar",
|
||||||
|
type: actor.icon.mediaType,
|
||||||
|
href: actor.icon.url
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -309,11 +295,11 @@ defmodule Nulla.ActivityPub do
|
||||||
def outbox(domain, username, total) do
|
def outbox(domain, username, total) do
|
||||||
Jason.OrderedObject.new(
|
Jason.OrderedObject.new(
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
id: "https://#{domain}/@#{username}/outbox",
|
id: "https://#{domain}/users/#{username}/outbox",
|
||||||
type: "OrderedCollection",
|
type: "OrderedCollection",
|
||||||
totalItems: total,
|
totalItems: total,
|
||||||
first: "https://#{domain}/@#{username}/outbox?page=true",
|
first: "https://#{domain}/users/#{username}/outbox?page=true",
|
||||||
last: "https://#{domain}/@#{username}/outbox?min_id=0&page=true"
|
last: "https://#{domain}/users/#{username}/outbox?min_id=0&page=true"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -328,32 +314,49 @@ defmodule Nulla.ActivityPub do
|
||||||
Hashtag: "as:Hashtag"
|
Hashtag: "as:Hashtag"
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
id: "https://#{domain}/@#{username}/outbox?page=true",
|
id: "https://#{domain}/users/#{username}/outbox?page=true",
|
||||||
type: "OrderedCollectionPage",
|
type: "OrderedCollectionPage",
|
||||||
next: "https://#{domain}/@#{username}/outbox?max_id=#{max_id}&page=true",
|
next: "https://#{domain}/users/#{username}/outbox?max_id=#{max_id}&page=true",
|
||||||
prev: "https://#{domain}/@#{username}/outbox?min_id=#{min_id}&page=true",
|
prev: "https://#{domain}/users/#{username}/outbox?min_id=#{min_id}&page=true",
|
||||||
partOf: "https://#{domain}/@#{username}/outbox",
|
partOf: "https://#{domain}/users/#{username}/outbox",
|
||||||
orderedItems: items
|
orderedItems: items
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec render_activity(String.t(), Note.t()) :: Jason.OrderedObject.t()
|
@spec activity_note(Nulla.Models.Note.t()) :: Jason.OrderedObject.t()
|
||||||
def render_activity(domain, note) do
|
def activity_note(note) do
|
||||||
Jason.OrderedObject.new(
|
Jason.OrderedObject.new(
|
||||||
id: "https://#{domain}/@#{note.user.username}/#{note.id}/activity",
|
id:
|
||||||
|
"https://#{note.actor.domain}/users/#{note.actor.preferredUsername}/#{note.id}/activity",
|
||||||
type: "Create",
|
type: "Create",
|
||||||
actor: "https://#{domain}/@#{note.user.username}",
|
actor: "https://#{note.actor.domain}/users/#{note.actor.preferredUsername}",
|
||||||
published: note.inserted_at |> DateTime.to_iso8601(),
|
published: note.inserted_at |> DateTime.to_iso8601(),
|
||||||
to: ["https://www.w3.org/ns/activitystreams#Public"],
|
to: [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
object:
|
object:
|
||||||
Jason.OrderedObject.new(
|
Jason.OrderedObject.new(
|
||||||
id: "https://#{domain}/@#{note.user.username}/#{note.id}",
|
id:
|
||||||
|
"https://#{note.actor.domain}/users/#{note.actor.preferredUsername}/statuses/#{note.id}",
|
||||||
type: "Note",
|
type: "Note",
|
||||||
content: note.content,
|
content: note.content,
|
||||||
published: note.inserted_at |> DateTime.to_iso8601(),
|
published: note.inserted_at |> DateTime.to_iso8601(),
|
||||||
attributedTo: "https://#{domain}/@#{note.user.username}",
|
attributedTo: "https://#{note.actor.domain}/users/#{note.actor.preferredUsername}",
|
||||||
to: ["https://www.w3.org/ns/activitystreams#Public"]
|
to: [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec follow_accept(Nulla.Models.Activity.t()) :: Jason.OrderedObject.t()
|
||||||
|
def follow_accept(activity) do
|
||||||
|
Jason.OrderedObject.new(
|
||||||
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
|
id: activity.ap_id,
|
||||||
|
type: activity.type,
|
||||||
|
actor: activity.actor,
|
||||||
|
object: activity.object
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
5
lib/nulla/httpsignature.ex
Normal file
5
lib/nulla/httpsignature.ex
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
defmodule Nulla.HTTPSignature do
|
||||||
|
def verify(_conn, _actor) do
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,9 +1,11 @@
|
||||||
defmodule Nulla.Models.Activity do
|
defmodule Nulla.Models.Activity do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
alias Nulla.SnowFlake
|
||||||
|
|
||||||
@primary_key {:id, :integer, autogenerate: false}
|
@primary_key {:id, :integer, autogenerate: false}
|
||||||
schema "activities" do
|
schema "activities" do
|
||||||
|
field :ap_id, :string
|
||||||
field :type, :string
|
field :type, :string
|
||||||
field :actor, :string
|
field :actor, :string
|
||||||
field :object, :map
|
field :object, :map
|
||||||
|
@ -15,8 +17,17 @@ defmodule Nulla.Models.Activity do
|
||||||
@doc false
|
@doc false
|
||||||
def changeset(activity, attrs) do
|
def changeset(activity, attrs) do
|
||||||
activity
|
activity
|
||||||
|> cast(attrs, [:type, :actor, :object, :to])
|
|> cast(attrs, [:ap_id, :type, :actor, :object, :to])
|
||||||
|> validate_required([:type, :actor, :object])
|
|> validate_required([:ap_id, :type, :actor, :object])
|
||||||
|> validate_inclusion(:type, ~w(Create Update Delete Undo Like Announce Follow Accept Reject))
|
|> validate_inclusion(:type, ~w(Create Update Delete Undo Like Announce Follow Accept Reject))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_activity(attrs) do
|
||||||
|
id = Snowflake.next_id()
|
||||||
|
|
||||||
|
%__MODULE__{}
|
||||||
|
|> __MODULE__.changeset(attrs)
|
||||||
|
|> Ecto.Changeset.put_change(:id, id)
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
defmodule Nulla.Models.Actor do
|
defmodule Nulla.Models.Actor do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Ecto.Query
|
|
||||||
alias Nulla.Repo
|
alias Nulla.Repo
|
||||||
alias Nulla.Snowflake
|
alias Nulla.Snowflake
|
||||||
alias Nulla.Models.User
|
|
||||||
alias Nulla.Models.Note
|
alias Nulla.Models.Note
|
||||||
|
|
||||||
@primary_key {:id, :integer, autogenerate: false}
|
@primary_key {:id, :integer, autogenerate: false}
|
||||||
schema "actors" do
|
schema "actors" do
|
||||||
|
field :domain, :string
|
||||||
|
field :ap_id, :string
|
||||||
field :type, :string
|
field :type, :string
|
||||||
field :following, :string
|
field :following, :string
|
||||||
field :followers, :string
|
field :followers, :string
|
||||||
|
@ -34,7 +34,6 @@ defmodule Nulla.Models.Actor do
|
||||||
field :vcard_bday, :date
|
field :vcard_bday, :date
|
||||||
field :vcard_Address, :string
|
field :vcard_Address, :string
|
||||||
|
|
||||||
has_one :user, User
|
|
||||||
has_many :notes, Note
|
has_many :notes, Note
|
||||||
has_many :media_attachments, through: [:notes, :media_attachments]
|
has_many :media_attachments, through: [:notes, :media_attachments]
|
||||||
end
|
end
|
||||||
|
@ -44,6 +43,8 @@ defmodule Nulla.Models.Actor do
|
||||||
actor
|
actor
|
||||||
|> cast(attrs, [
|
|> cast(attrs, [
|
||||||
:id,
|
:id,
|
||||||
|
:domain,
|
||||||
|
:ap_id,
|
||||||
:type,
|
:type,
|
||||||
:following,
|
:following,
|
||||||
:followers,
|
:followers,
|
||||||
|
@ -71,6 +72,8 @@ defmodule Nulla.Models.Actor do
|
||||||
])
|
])
|
||||||
|> validate_required([
|
|> validate_required([
|
||||||
:id,
|
:id,
|
||||||
|
:domain,
|
||||||
|
:ap_id,
|
||||||
:type,
|
:type,
|
||||||
:following,
|
:following,
|
||||||
:followers,
|
:followers,
|
||||||
|
@ -98,7 +101,7 @@ defmodule Nulla.Models.Actor do
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_user(attrs) when is_map(attrs) do
|
def create_actor(attrs) when is_map(attrs) do
|
||||||
id = Snowflake.next_id()
|
id = Snowflake.next_id()
|
||||||
|
|
||||||
%__MODULE__{}
|
%__MODULE__{}
|
||||||
|
@ -106,4 +109,8 @@ defmodule Nulla.Models.Actor do
|
||||||
|> Ecto.Changeset.put_change(:id, id)
|
|> Ecto.Changeset.put_change(:id, id)
|
||||||
|> Repo.insert()
|
|> Repo.insert()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_actor(username, domain) do
|
||||||
|
Repo.get_by(__MODULE__, preferredUsername: username, domain: domain)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
defmodule Nulla.Models.Follow do
|
defmodule Nulla.Models.Follow do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
alias Nulla.Repo
|
||||||
alias Nulla.Snowflake
|
alias Nulla.Snowflake
|
||||||
alias Nulla.Models.Follow
|
alias Nulla.Models.Actor
|
||||||
|
|
||||||
@primary_key {:id, :integer, autogenerate: false}
|
@primary_key {:id, :integer, autogenerate: false}
|
||||||
schema "follows" do
|
schema "follows" do
|
||||||
belongs_to :user, Nulla.Models.User
|
belongs_to :follower, Actor
|
||||||
belongs_to :target, Nulla.Models.User
|
belongs_to :followed, Actor
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
@ -23,8 +24,8 @@ defmodule Nulla.Models.Follow do
|
||||||
def create_follow(attrs) do
|
def create_follow(attrs) do
|
||||||
id = Snowflake.next_id()
|
id = Snowflake.next_id()
|
||||||
|
|
||||||
%Follow{}
|
%__MODULE__{}
|
||||||
|> Follow.changeset(attrs)
|
|> __MODULE__.changeset(attrs)
|
||||||
|> Ecto.Changeset.put_change(:id, id)
|
|> Ecto.Changeset.put_change(:id, id)
|
||||||
|> Repo.insert()
|
|> Repo.insert()
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,8 @@ defmodule Nulla.Models.Note do
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
alias Nulla.Repo
|
alias Nulla.Repo
|
||||||
alias Nulla.Models.Note
|
alias Nulla.Models.Actor
|
||||||
|
alias Nulla.Models.MediaAttachment
|
||||||
|
|
||||||
@primary_key {:id, :integer, autogenerate: false}
|
@primary_key {:id, :integer, autogenerate: false}
|
||||||
schema "notes" do
|
schema "notes" do
|
||||||
|
@ -17,8 +18,8 @@ defmodule Nulla.Models.Note do
|
||||||
field :language, :string
|
field :language, :string
|
||||||
field :in_reply_to, :string
|
field :in_reply_to, :string
|
||||||
|
|
||||||
belongs_to :user, Nulla.Models.User
|
belongs_to :actor, Actor
|
||||||
has_many :media_attachments, Nulla.Models.MediaAttachment
|
has_many :media_attachments, MediaAttachment
|
||||||
|
|
||||||
timestamps(type: :utc_datetime)
|
timestamps(type: :utc_datetime)
|
||||||
end
|
end
|
||||||
|
@ -26,32 +27,32 @@ defmodule Nulla.Models.Note do
|
||||||
@doc false
|
@doc false
|
||||||
def changeset(note, attrs) do
|
def changeset(note, attrs) do
|
||||||
note
|
note
|
||||||
|> cast(attrs, [:content, :visibility, :sensitive, :language, :in_reply_to, :user_id])
|
|> cast(attrs, [:content, :visibility, :sensitive, :language, :in_reply_to, :actor_id])
|
||||||
|> validate_required([:content, :visibility, :sensitive, :language, :in_reply_to, :user_id])
|
|> validate_required([:content, :visibility, :sensitive, :language, :in_reply_to, :actor_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_note!(id), do: Repo.get!(Note, id)
|
def get_note!(id), do: Repo.get!(__MODULE__, id)
|
||||||
|
|
||||||
def get_latest_notes(user_id, limit \\ 20) do
|
def get_latest_notes(actor_id, limit \\ 20) do
|
||||||
from(n in Note,
|
from(n in __MODULE__,
|
||||||
where: n.user_id == ^user_id,
|
where: n.actor_id == ^actor_id,
|
||||||
order_by: [desc: n.inserted_at],
|
order_by: [desc: n.inserted_at],
|
||||||
limit: ^limit
|
limit: ^limit
|
||||||
)
|
)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_before_notes(user_id, max_id, limit \\ 20) do
|
def get_before_notes(actor_id, max_id, limit \\ 20) do
|
||||||
from(n in Note,
|
from(n in __MODULE__,
|
||||||
where: n.user_id == ^user_id and n.id < ^max_id,
|
where: n.actor_id == ^actor_id and n.id < ^max_id,
|
||||||
order_by: [desc: n.inserted_at],
|
order_by: [desc: n.inserted_at],
|
||||||
limit: ^limit
|
limit: ^limit
|
||||||
)
|
)
|
||||||
|> Nulla.Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_total_notes_count(user_id) do
|
def get_total_notes_count(actor_id) do
|
||||||
from(n in Note, where: n.user_id == ^user_id)
|
from(n in __MODULE__, where: n.actor_id == ^actor_id)
|
||||||
|> Repo.aggregate(:count, :id)
|
|> Repo.aggregate(:count, :id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
30
lib/nulla/models/relation.ex
Normal file
30
lib/nulla/models/relation.ex
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
defmodule Nulla.Models.Relation do
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.Changeset
|
||||||
|
alias Nulla.Models.Actor
|
||||||
|
alias Nulla.Models.Activity
|
||||||
|
|
||||||
|
@primary_key {:id, :integer, autogenerate: false}
|
||||||
|
schema "relations" do
|
||||||
|
field :type, :string
|
||||||
|
field :status, :string
|
||||||
|
|
||||||
|
belongs_to :source, Actor, foreign_key: :source_id, type: :integer
|
||||||
|
belongs_to :target, Actor, foreign_key: :target_id, type: :integer
|
||||||
|
belongs_to :activity, Activity, foreign_key: :activity_id, type: :integer
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(relation, attrs) do
|
||||||
|
relation
|
||||||
|
|> cast(attrs, [:id, :source_id, :target_id, :type, :status, :activity_id])
|
||||||
|
|> validate_required([:id, :source_id, :target_id, :type])
|
||||||
|
|> validate_inclusion(:type, ~w(follow block mute friend_request))
|
||||||
|
|> validate_inclusion(:status, ~w(pending accepted rejected active))
|
||||||
|
|> foreign_key_constraint(:source_id)
|
||||||
|
|> foreign_key_constraint(:target_id)
|
||||||
|
|> foreign_key_constraint(:activity_id)
|
||||||
|
|> unique_constraint([:source_id, :target_id, :type])
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,7 +3,6 @@ defmodule Nulla.Models.User do
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
alias Nulla.Repo
|
alias Nulla.Repo
|
||||||
alias Nulla.Snowflake
|
|
||||||
alias Nulla.Models.User
|
alias Nulla.Models.User
|
||||||
alias Nulla.Models.Actor
|
alias Nulla.Models.Actor
|
||||||
alias Nulla.Models.Session
|
alias Nulla.Models.Session
|
||||||
|
@ -48,8 +47,6 @@ 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_user_by_username!(username), do: Repo.get_by!(User, username: username)
|
|
||||||
|
|
||||||
def get_user_by_username_and_domain(username, domain) do
|
def get_user_by_username_and_domain(username, domain) do
|
||||||
from(u in User,
|
from(u in User,
|
||||||
where: u.username == ^username and u.domain == ^domain
|
where: u.username == ^username and u.domain == ^domain
|
||||||
|
|
43
lib/nulla_web/controllers/actor_controller.ex
Normal file
43
lib/nulla_web/controllers/actor_controller.ex
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
defmodule NullaWeb.ActorController do
|
||||||
|
use NullaWeb, :controller
|
||||||
|
alias Nulla.ActivityPub
|
||||||
|
alias Nulla.Utils
|
||||||
|
alias Nulla.Models.Actor
|
||||||
|
alias Nulla.Models.Note
|
||||||
|
alias Nulla.Models.InstanceSettings
|
||||||
|
|
||||||
|
def show(conn, %{"username" => username}) do
|
||||||
|
accept = List.first(get_req_header(conn, "accept"))
|
||||||
|
instance_settings = InstanceSettings.get_instance_settings!()
|
||||||
|
domain = instance_settings.domain
|
||||||
|
|
||||||
|
case Actor.get_actor(username, domain) do
|
||||||
|
nil ->
|
||||||
|
conn
|
||||||
|
|> put_status(:not_found)
|
||||||
|
|> json(%{error: "Not Found"})
|
||||||
|
|
||||||
|
%Actor{} = actor ->
|
||||||
|
if accept in ["application/activity+json", "application/ld+json"] do
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/activity+json")
|
||||||
|
|> send_resp(200, Jason.encode!(ActivityPub.actor(actor)))
|
||||||
|
else
|
||||||
|
notes = Note.get_latest_notes(actor.id)
|
||||||
|
following = Utils.count_following_by_username!(actor.preferredUsername)
|
||||||
|
followers = Utils.count_followers_by_username!(actor.preferredUsername)
|
||||||
|
|
||||||
|
render(
|
||||||
|
conn,
|
||||||
|
:show,
|
||||||
|
domain: domain,
|
||||||
|
actor: actor,
|
||||||
|
notes: notes,
|
||||||
|
following: following,
|
||||||
|
followers: followers,
|
||||||
|
layout: false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
12
lib/nulla_web/controllers/auth_controller.ex
Normal file
12
lib/nulla_web/controllers/auth_controller.ex
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
defmodule NullaWeb.AuthController do
|
||||||
|
use NullaWeb, :controller
|
||||||
|
|
||||||
|
def sign_in do
|
||||||
|
end
|
||||||
|
|
||||||
|
def sign_out do
|
||||||
|
end
|
||||||
|
|
||||||
|
def sign_up do
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,15 +2,15 @@ defmodule NullaWeb.FollowController do
|
||||||
use NullaWeb, :controller
|
use NullaWeb, :controller
|
||||||
alias Nulla.ActivityPub
|
alias Nulla.ActivityPub
|
||||||
alias Nulla.Utils
|
alias Nulla.Utils
|
||||||
alias Nulla.Models.User
|
alias Nulla.Models.Actor
|
||||||
alias Nulla.Models.InstanceSettings
|
alias Nulla.Models.InstanceSettings
|
||||||
|
|
||||||
def following(conn, %{"username" => username, "page" => page_param}) do
|
def following(conn, %{"username" => username, "page" => page_param}) do
|
||||||
instance_settings = InstanceSettings.get_instance_settings!()
|
instance_settings = InstanceSettings.get_instance_settings!()
|
||||||
domain = instance_settings.domain
|
domain = instance_settings.domain
|
||||||
offset = instance_settings.offset
|
offset = instance_settings.offset
|
||||||
user = User.get_user_by_username!(username)
|
actor = Actor.get_actor(username, domain)
|
||||||
total = Utils.count_following_by_username!(user.username)
|
total = Utils.count_following_by_username!(actor.preferredUsername)
|
||||||
|
|
||||||
page =
|
page =
|
||||||
case Integer.parse(page_param) do
|
case Integer.parse(page_param) do
|
||||||
|
@ -18,30 +18,30 @@ defmodule NullaWeb.FollowController do
|
||||||
_ -> 1
|
_ -> 1
|
||||||
end
|
end
|
||||||
|
|
||||||
following_list = Utils.get_following_users_by_username!(user.username, page)
|
following_list = Utils.get_following_users_by_username!(actor.preferredUsername, page)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/activity+json")
|
|> put_resp_content_type("application/activity+json")
|
||||||
|> json(ActivityPub.following(domain, user, total, following_list, page, offset))
|
|> json(ActivityPub.following(domain, actor, total, following_list, page, offset))
|
||||||
end
|
end
|
||||||
|
|
||||||
def following(conn, %{"username" => username}) do
|
def following(conn, %{"username" => username}) do
|
||||||
instance_settings = InstanceSettings.get_instance_settings!()
|
instance_settings = InstanceSettings.get_instance_settings!()
|
||||||
domain = instance_settings.domain
|
domain = instance_settings.domain
|
||||||
user = User.get_user_by_username!(username)
|
actor = Actor.get_actor(username, domain)
|
||||||
total = Utils.count_following_by_username!(user.username)
|
total = Utils.count_following_by_username!(actor.preferredUsername)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/activity+json")
|
|> put_resp_content_type("application/activity+json")
|
||||||
|> json(ActivityPub.following(domain, user, total))
|
|> json(ActivityPub.following(domain, actor, total))
|
||||||
end
|
end
|
||||||
|
|
||||||
def followers(conn, %{"username" => username, "page" => page_param}) do
|
def followers(conn, %{"username" => username, "page" => page_param}) do
|
||||||
instance_settings = InstanceSettings.get_instance_settings!()
|
instance_settings = InstanceSettings.get_instance_settings!()
|
||||||
domain = instance_settings.domain
|
domain = instance_settings.domain
|
||||||
offset = instance_settings.offset
|
offset = instance_settings.offset
|
||||||
user = User.get_user_by_username!(username)
|
actor = Actor.get_actor(username, domain)
|
||||||
total = Utils.count_followers_by_username!(user.username)
|
total = Utils.count_followers_by_username!(actor.preferredUsername)
|
||||||
|
|
||||||
page =
|
page =
|
||||||
case Integer.parse(page_param) do
|
case Integer.parse(page_param) do
|
||||||
|
@ -49,21 +49,21 @@ defmodule NullaWeb.FollowController do
|
||||||
_ -> 1
|
_ -> 1
|
||||||
end
|
end
|
||||||
|
|
||||||
followers_list = Utils.get_followers_by_username!(user.username, page)
|
followers_list = Utils.get_followers_by_username!(actor.preferredUsername, page)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/activity+json")
|
|> put_resp_content_type("application/activity+json")
|
||||||
|> json(ActivityPub.followers(domain, user, total, followers_list, page, offset))
|
|> json(ActivityPub.followers(domain, actor, total, followers_list, page, offset))
|
||||||
end
|
end
|
||||||
|
|
||||||
def followers(conn, %{"username" => username}) do
|
def followers(conn, %{"username" => username}) do
|
||||||
instance_settings = InstanceSettings.get_instance_settings!()
|
instance_settings = InstanceSettings.get_instance_settings!()
|
||||||
domain = instance_settings.domain
|
domain = instance_settings.domain
|
||||||
user = User.get_user_by_username!(username)
|
actor = Actor.get_actor(username, domain)
|
||||||
total = Utils.count_followers_by_username!(user.username)
|
total = Utils.count_followers_by_username!(actor.preferredUsername)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/activity+json")
|
|> put_resp_content_type("application/activity+json")
|
||||||
|> json(ActivityPub.followers(domain, user, total))
|
|> json(ActivityPub.followers(domain, actor, total))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,19 +1,49 @@
|
||||||
defmodule NullaWeb.InboxController do
|
defmodule NullaWeb.InboxController do
|
||||||
use NullaWeb, :controller
|
use NullaWeb, :controller
|
||||||
alias Nulla.Models.Follow
|
alias Nulla.HTTPSignature
|
||||||
|
alias Nulla.ActivityPub
|
||||||
alias Nulla.Utils
|
alias Nulla.Utils
|
||||||
|
alias Nulla.Models.Actor
|
||||||
|
alias Nulla.Models.Relation
|
||||||
|
|
||||||
def inbox(
|
def inbox(
|
||||||
conn,
|
conn,
|
||||||
%{"type" => "Follow", "actor" => actor_uri, "object" => target_uri} = activity
|
%{"id" => follow_id, "type" => "Follow", "actor" => actor_uri, "object" => target_uri}
|
||||||
) do
|
) do
|
||||||
with {:ok, target_user} <- Utils.resolve_local_actor(target_uri),
|
with {:ok, target_actor} <- Utils.resolve_local_actor(target_uri),
|
||||||
{:ok, remote_actor} <- Utils.fetch_remote_actor(actor_uri),
|
{:ok, remote_actor_json} <- Utils.fetch_remote_actor(actor_uri),
|
||||||
:ok <- HTTPSignature.verify(conn, remote_actor),
|
:ok <- HTTPSignature.verify(conn, remote_actor_json),
|
||||||
remote_user <- Follow.create_remote_user(remote_actor),
|
remote_actor <-
|
||||||
follow <- Follow.create_follow(%{user: remote_user, target: target_user}),
|
Actor.create_actor(
|
||||||
:ok <- Utils.send_accept_activity(remote_actor, target_user, follow, activity) do
|
remote_actor_json
|
||||||
json(conn, %{"status" => "Follow accepted"})
|
|> Map.put("ap_id", remote_actor_json["id"])
|
||||||
|
|> Map.delete("id")
|
||||||
|
|> Map.put("domain", URI.parse(remote_actor_json["id"]).host)
|
||||||
|
),
|
||||||
|
follow_activity <-
|
||||||
|
Activity.create_activity(%{
|
||||||
|
ap_id: follow_id,
|
||||||
|
type: "Follow",
|
||||||
|
actor: actor_uri,
|
||||||
|
object: target_uri
|
||||||
|
}),
|
||||||
|
accept_activity <-
|
||||||
|
Activity.create_activity(%{
|
||||||
|
type: "Accept",
|
||||||
|
actor: target_uri,
|
||||||
|
object: follow_activity
|
||||||
|
}),
|
||||||
|
relation <- Relation.create_relation(%{
|
||||||
|
id: 1,
|
||||||
|
follower: remote_actor.id,
|
||||||
|
followed: target_actor.id
|
||||||
|
}) do
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/activity+json")
|
||||||
|
|> send_resp(
|
||||||
|
200,
|
||||||
|
Jason.encode!(ActivityPub.follow_accept(accept_activity))
|
||||||
|
)
|
||||||
else
|
else
|
||||||
error ->
|
error ->
|
||||||
IO.inspect(error, label: "Follow error")
|
IO.inspect(error, label: "Follow error")
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
defmodule NullaWeb.NodeinfoController do
|
defmodule NullaWeb.NodeinfoController do
|
||||||
use NullaWeb, :controller
|
use NullaWeb, :controller
|
||||||
alias Nulla.Repo
|
|
||||||
alias Nulla.ActivityPub
|
alias Nulla.ActivityPub
|
||||||
alias Nulla.Models.User
|
alias Nulla.Models.User
|
||||||
alias Nulla.Models.InstanceSettings
|
alias Nulla.Models.InstanceSettings
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
defmodule NullaWeb.OutboxController do
|
defmodule NullaWeb.OutboxController do
|
||||||
use NullaWeb, :controller
|
use NullaWeb, :controller
|
||||||
alias Nulla.ActivityPub
|
alias Nulla.ActivityPub
|
||||||
alias Nulla.Models.User
|
alias Nulla.Models.Actor
|
||||||
alias Nulla.Models.Note
|
alias Nulla.Models.Note
|
||||||
alias Nulla.Models.InstanceSettings
|
alias Nulla.Models.InstanceSettings
|
||||||
|
|
||||||
|
@ -10,17 +10,17 @@ defmodule NullaWeb.OutboxController do
|
||||||
"true" ->
|
"true" ->
|
||||||
instance_settings = InstanceSettings.get_instance_settings!()
|
instance_settings = InstanceSettings.get_instance_settings!()
|
||||||
domain = instance_settings.domain
|
domain = instance_settings.domain
|
||||||
user = User.get_user_by_username!(username)
|
actor = Actor.get_actor(username, domain)
|
||||||
max_id = params["max_id"] && String.to_integer(params["max_id"])
|
max_id = params["max_id"] && String.to_integer(params["max_id"])
|
||||||
|
|
||||||
notes =
|
notes =
|
||||||
if max_id do
|
if max_id do
|
||||||
Note.get_before_notes(user.id, max_id)
|
Note.get_before_notes(actor.id, max_id)
|
||||||
else
|
else
|
||||||
Note.get_latest_notes(user.id)
|
Note.get_latest_notes(actor.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
items = Enum.map(notes, &ActivityPub.render_activity(&1, domain))
|
items = Enum.map(notes, &ActivityPub.activity_note(&1))
|
||||||
|
|
||||||
next_max_id =
|
next_max_id =
|
||||||
case List.last(notes) do
|
case List.last(notes) do
|
||||||
|
@ -44,8 +44,8 @@ defmodule NullaWeb.OutboxController do
|
||||||
_ ->
|
_ ->
|
||||||
instance_settings = InstanceSettings.get_instance_settings!()
|
instance_settings = InstanceSettings.get_instance_settings!()
|
||||||
domain = instance_settings.domain
|
domain = instance_settings.domain
|
||||||
user = User.get_user_by_username!(username)
|
actor = Actor.get_actor(username, domain)
|
||||||
total = Note.get_total_notes_count(user.id)
|
total = Note.get_total_notes_count(actor.id)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/activity+json")
|
|> put_resp_content_type("application/activity+json")
|
||||||
|
|
|
@ -1,36 +1,3 @@
|
||||||
defmodule NullaWeb.UserController do
|
defmodule NullaWeb.UserController do
|
||||||
use NullaWeb, :controller
|
use NullaWeb, :controller
|
||||||
alias Nulla.ActivityPub
|
|
||||||
alias Nulla.Utils
|
|
||||||
alias Nulla.Models.User
|
|
||||||
alias Nulla.Models.Note
|
|
||||||
alias Nulla.Models.InstanceSettings
|
|
||||||
|
|
||||||
def show(conn, %{"username" => username}) do
|
|
||||||
accept = List.first(get_req_header(conn, "accept"))
|
|
||||||
instance_settings = InstanceSettings.get_instance_settings!()
|
|
||||||
domain = instance_settings.domain
|
|
||||||
user = User.get_user_by_username!(username)
|
|
||||||
notes = Note.get_notes(user.id)
|
|
||||||
|
|
||||||
if accept in ["application/activity+json", "application/ld+json"] do
|
|
||||||
conn
|
|
||||||
|> put_resp_content_type("application/activity+json")
|
|
||||||
|> 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)
|
|
||||||
|
|
||||||
render(
|
|
||||||
conn,
|
|
||||||
:show,
|
|
||||||
domain: domain,
|
|
||||||
user: user,
|
|
||||||
notes: notes,
|
|
||||||
following: following,
|
|
||||||
followers: followers,
|
|
||||||
layout: false
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,24 +1,23 @@
|
||||||
defmodule NullaWeb.WebfingerController do
|
defmodule NullaWeb.WebfingerController do
|
||||||
use NullaWeb, :controller
|
use NullaWeb, :controller
|
||||||
alias Nulla.Repo
|
|
||||||
alias Nulla.ActivityPub
|
alias Nulla.ActivityPub
|
||||||
alias Nulla.Models.User
|
alias Nulla.Models.Actor
|
||||||
alias Nulla.Models.InstanceSettings
|
alias Nulla.Models.InstanceSettings
|
||||||
|
|
||||||
def index(conn, %{"resource" => resource}) do
|
def index(conn, %{"resource" => resource}) do
|
||||||
case Regex.run(~r/^acct:([^@]+)@(.+)$/, resource) do
|
case Regex.run(~r/^acct:([^@]+)@(.+)$/, resource) do
|
||||||
[_, username, domain] ->
|
[_, username, domain] ->
|
||||||
case User.get_user_by_username(username) do
|
case Actor.get_actor(username, domain) do
|
||||||
nil ->
|
nil ->
|
||||||
conn
|
conn
|
||||||
|> put_status(:not_found)
|
|> put_status(:not_found)
|
||||||
|> json(%{error: "Not Found"})
|
|> json(%{error: "Not Found"})
|
||||||
|
|
||||||
user ->
|
%Actor{} = actor ->
|
||||||
instance_settings = InstanceSettings.get_instance_settings!()
|
instance_settings = InstanceSettings.get_instance_settings!()
|
||||||
|
|
||||||
if domain == instance_settings.domain do
|
if domain == instance_settings.domain do
|
||||||
json(conn, ActivityPub.webfinger(domain, username, resource))
|
json(conn, ActivityPub.webfinger(actor))
|
||||||
else
|
else
|
||||||
conn
|
conn
|
||||||
|> put_status(:not_found)
|
|> put_status(:not_found)
|
||||||
|
|
|
@ -29,7 +29,7 @@ defmodule NullaWeb.Router do
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/users/:username" do
|
scope "/users/:username" do
|
||||||
get "/", UserController, :show
|
get "/", ActorController, :show
|
||||||
get "/following", FollowController, :following
|
get "/following", FollowController, :following
|
||||||
get "/followers", FollowController, :followers
|
get "/followers", FollowController, :followers
|
||||||
post "/inbox", InboxController, :inbox
|
post "/inbox", InboxController, :inbox
|
||||||
|
@ -38,7 +38,7 @@ defmodule NullaWeb.Router do
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/@:username" do
|
scope "/@:username" do
|
||||||
get "/", UserController, :show
|
get "/", ActorController, :show
|
||||||
get "/following", FollowController, :following
|
get "/following", FollowController, :following
|
||||||
get "/followers", FollowController, :followers
|
get "/followers", FollowController, :followers
|
||||||
post "/inbox", InboxController, :inbox
|
post "/inbox", InboxController, :inbox
|
||||||
|
|
|
@ -4,14 +4,16 @@ defmodule Nulla.Repo.Migrations.CreateActors do
|
||||||
def change do
|
def change do
|
||||||
create table(:actors, primary_key: false) do
|
create table(:actors, primary_key: false) do
|
||||||
add :id, :bigint, primary_key: true
|
add :id, :bigint, primary_key: true
|
||||||
add :type, :string
|
add :domain, :string
|
||||||
add :following, :string
|
add :ap_id, :string, null: false
|
||||||
add :followers, :string
|
add :type, :string, null: false
|
||||||
add :inbox, :string
|
add :following, :string, null: false
|
||||||
add :outbox, :string
|
add :followers, :string, null: false
|
||||||
|
add :inbox, :string, null: false
|
||||||
|
add :outbox, :string, null: false
|
||||||
add :featured, :string
|
add :featured, :string
|
||||||
add :featuredTags, :string
|
add :featuredTags, :string
|
||||||
add :preferredUsername, :string
|
add :preferredUsername, :string, null: false
|
||||||
add :name, :string
|
add :name, :string
|
||||||
add :summary, :string
|
add :summary, :string
|
||||||
add :url, :string
|
add :url, :string
|
||||||
|
|
|
@ -9,11 +9,11 @@ defmodule Nulla.Repo.Migrations.CreateNotes do
|
||||||
add :sensitive, :boolean, default: false
|
add :sensitive, :boolean, default: false
|
||||||
add :language, :string
|
add :language, :string
|
||||||
add :in_reply_to, :string
|
add :in_reply_to, :string
|
||||||
add :user_id, references(:users, on_delete: :delete_all)
|
add :actor_id, references(:actors, on_delete: :delete_all)
|
||||||
|
|
||||||
timestamps(type: :utc_datetime)
|
timestamps(type: :utc_datetime)
|
||||||
end
|
end
|
||||||
|
|
||||||
create index(:notes, [:user_id])
|
create index(:notes, [:actor_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,13 +4,13 @@ defmodule Nulla.Repo.Migrations.CreateFollows do
|
||||||
def change do
|
def change do
|
||||||
create table(:follows, primary_key: false) do
|
create table(:follows, primary_key: false) do
|
||||||
add :id, :bigint, primary_key: true
|
add :id, :bigint, primary_key: true
|
||||||
add :user_id, references(:users, on_delete: :delete_all), null: false
|
add :follower_id, references(:actors, on_delete: :delete_all), null: false
|
||||||
add :target_id, references(:users, on_delete: :delete_all), null: false
|
add :following_id, references(:actors, on_delete: :delete_all), null: false
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
create unique_index(:follows, [:user_id, :target_id])
|
create unique_index(:follows, [:follower_id, :following_id])
|
||||||
create index(:follows, [:target_id])
|
create index(:follows, [:following_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,15 +4,16 @@ defmodule Nulla.Repo.Migrations.CreateActivities do
|
||||||
def change do
|
def change do
|
||||||
create table(:activities, primary_key: false) do
|
create table(:activities, primary_key: false) do
|
||||||
add :id, :bigint, primary_key: true
|
add :id, :bigint, primary_key: true
|
||||||
|
add :ap_id, :string, null: false
|
||||||
add :type, :string, null: false
|
add :type, :string, null: false
|
||||||
add :actor, :string, null: false
|
add :actor_id, references(:actors, type: :bigint, on_delete: :nothing), null: false
|
||||||
add :object, :map, null: false
|
add :object, :map, null: false
|
||||||
add :to, {:array, :string}, default: []
|
add :to, {:array, :string}, default: []
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
create index(:activities, [:actor])
|
|
||||||
create index(:activities, [:type])
|
create index(:activities, [:type])
|
||||||
|
create index(:activities, [:actor_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
23
priv/repo/migrations/20250617091354_create_relations.exs
Normal file
23
priv/repo/migrations/20250617091354_create_relations.exs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
defmodule Nulla.Repo.Migrations.CreateActorRelations do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:relations, primary_key: false) do
|
||||||
|
add :id, :bigint, primary_key: true
|
||||||
|
add :source_id, :bigint, null: false
|
||||||
|
add :target_id, :bigint, null: false
|
||||||
|
add :type, :string, null: false
|
||||||
|
add :status, :string, null: false
|
||||||
|
add :activity_id, :bigint
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create index(:relations, [:source_id])
|
||||||
|
create index(:relations, [:target_id])
|
||||||
|
create index(:relations, [:type])
|
||||||
|
create index(:relations, [:activity_id])
|
||||||
|
|
||||||
|
create unique_index(:relations, [:source_id, :target_id, :type])
|
||||||
|
end
|
||||||
|
end
|
Loading…
Add table
Add a link
Reference in a new issue