feat: mastodon microblog syndication
This commit is contained in:
parent
96e778ff91
commit
9c01aef829
11 changed files with 153 additions and 15 deletions
config
lib
core
schema
web
priv/repo/migrations
|
@ -61,12 +61,16 @@ config :flop, repo: Core.Repo
|
|||
|
||||
config :tesla, adapter: Tesla.Adapter.Mint
|
||||
|
||||
mastodon_instance = "https://tech.lgbt"
|
||||
|
||||
config :sloanely_but_surely, Core.Syndication, mastodon_instance: mastodon_instance
|
||||
|
||||
config :ueberauth, Ueberauth,
|
||||
providers: [
|
||||
mastodon:
|
||||
{Ueberauth.Strategy.Mastodon,
|
||||
[
|
||||
instance: "https://tech.lgbt",
|
||||
instance: mastodon_instance,
|
||||
client_id: {System, :get_env, ["MASTODON_CLIENT_ID"]},
|
||||
client_secret: {System, :get_env, ["MASTODON_CLIENT_SECRET"]},
|
||||
scope: "read write push"
|
||||
|
|
|
@ -85,9 +85,14 @@ defmodule Core.Posts do
|
|||
end
|
||||
|
||||
def publish_post(%Schema.Post{} = post, published_at \\ DateTime.utc_now()) do
|
||||
post
|
||||
|> Post.publish_changeset(published_at)
|
||||
|> Core.Repo.update()
|
||||
with {:ok, post} <-
|
||||
post
|
||||
|> Post.publish_changeset(published_at)
|
||||
|> Core.Repo.update() do
|
||||
Core.Syndication.syndicate_to_mastodon(post)
|
||||
|
||||
{:ok, post}
|
||||
end
|
||||
end
|
||||
|
||||
def delete_post(%Schema.Post{} = post, deleted_at \\ DateTime.utc_now()) do
|
||||
|
|
|
@ -6,7 +6,16 @@ defmodule Core.Posts.Post do
|
|||
def content_changeset(%Schema.Post{} = post, attrs) do
|
||||
changeset =
|
||||
post
|
||||
|> cast(attrs, [:tid, :kind, :slug, :title, :body, :deleted_at, :published_at])
|
||||
|> cast(attrs, [
|
||||
:tid,
|
||||
:kind,
|
||||
:slug,
|
||||
:title,
|
||||
:body,
|
||||
:deleted_at,
|
||||
:published_at,
|
||||
:syndicate_to_mastodon
|
||||
])
|
||||
|> validate_required([:kind], message: "must have a kind")
|
||||
|> validate_required([:body], message: "must have a body")
|
||||
|
||||
|
@ -77,7 +86,11 @@ defmodule Core.Posts.Post do
|
|||
import Ecto.Query
|
||||
|
||||
def base do
|
||||
from _ in Schema.Post, as: :posts
|
||||
from p in Schema.Post,
|
||||
as: :posts,
|
||||
join: mp in assoc(p, :mastodon_post),
|
||||
as: :mastodon_posts,
|
||||
preload: [mastodon_post: mp]
|
||||
end
|
||||
|
||||
def current(query \\ base()) do
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
defmodule Core.Syndication do
|
||||
alias __MODULE__
|
||||
|
||||
@mastodon_instance Application.compile_env!(:sloanely_but_surely, [
|
||||
Core.Syndication,
|
||||
:mastodon_instance
|
||||
])
|
||||
|
||||
def get_mastodon_account(user) do
|
||||
Core.Repo.get_by(Schema.MastodonAccount, user_id: user.id)
|
||||
end
|
||||
|
@ -11,4 +16,38 @@ defmodule Core.Syndication do
|
|||
|> Syndication.MastodonAccount.changeset(attrs)
|
||||
|> Core.Repo.insert()
|
||||
end
|
||||
|
||||
def syndicate_to_mastodon(%Schema.Post{} = post) do
|
||||
post = Core.Repo.preload(post, [:mastodon_post])
|
||||
|
||||
if post.syndicate_to_mastodon and is_nil(post.mastodon_post) do
|
||||
conn = build_mastodon_client_conn()
|
||||
|
||||
{:ok, resp} = MastodonClient.post(conn, "/api/v1/statuses", %{status: post.body})
|
||||
|
||||
post
|
||||
|> Ecto.build_assoc(:mastodon_post)
|
||||
|> Syndication.MastodonPost.changeset(%{
|
||||
status_id: resp.body["id"],
|
||||
url: resp.body["url"]
|
||||
})
|
||||
|> Core.Repo.insert()
|
||||
|
||||
post
|
||||
else
|
||||
post
|
||||
end
|
||||
end
|
||||
|
||||
defp get_mastodon_access_token do
|
||||
mastodon_account = Core.Repo.one!(Schema.MastodonAccount)
|
||||
mastodon_account.access_token
|
||||
end
|
||||
|
||||
defp build_mastodon_client_conn do
|
||||
%MastodonClient.Conn{
|
||||
instance: @mastodon_instance,
|
||||
access_token: get_mastodon_access_token()
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
10
lib/core/syndication/mastodon_post.ex
Normal file
10
lib/core/syndication/mastodon_post.ex
Normal file
|
@ -0,0 +1,10 @@
|
|||
defmodule Core.Syndication.MastodonPost do
|
||||
import Ecto.Changeset
|
||||
|
||||
def changeset(mastodon_post, attrs) do
|
||||
mastodon_post
|
||||
|> cast(attrs, [:status_id, :url])
|
||||
|> validate_required([:status_id, :url])
|
||||
|> unique_constraint([:post_id])
|
||||
end
|
||||
end
|
11
lib/schema/mastodon_post.ex
Normal file
11
lib/schema/mastodon_post.ex
Normal file
|
@ -0,0 +1,11 @@
|
|||
defmodule Schema.MastodonPost do
|
||||
use Schema
|
||||
|
||||
schema "mastodon_posts" do
|
||||
field :status_id, :string
|
||||
field :url, :string
|
||||
|
||||
belongs_to :post, Schema.Post
|
||||
timestamps()
|
||||
end
|
||||
end
|
|
@ -28,6 +28,10 @@ defmodule Schema.Post do
|
|||
field :published_at, :utc_datetime_usec
|
||||
field :deleted_at, :utc_datetime_usec
|
||||
|
||||
field :syndicate_to_mastodon, :boolean
|
||||
|
||||
has_one :mastodon_post, Schema.MastodonPost
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ defmodule Web.CoreComponents do
|
|||
attr :label, :string, default: nil
|
||||
attr :value, :any
|
||||
attr :class, :string, default: nil
|
||||
attr :type, :string, default: "text", values: ~w[text password textarea datetime-local]
|
||||
attr :type, :string, default: "text", values: ~w[text password textarea datetime-local checkbox]
|
||||
attr :field, FormField
|
||||
attr :errors, :list, default: []
|
||||
attr :rest, :global, include: ~w[disabled form pattern placeholder readonly required]
|
||||
|
@ -35,6 +35,23 @@ defmodule Web.CoreComponents do
|
|||
|> input()
|
||||
end
|
||||
|
||||
def input(%{type: "checkbox"} = assigns) do
|
||||
~H"""
|
||||
<div class={["flex flex-row items-center gap-x-2", @class]}>
|
||||
<.label for={@id}>{@label}</.label>
|
||||
<input type="hidden" name={@name} value="false" disabled={@rest[:disabled]} />
|
||||
<input
|
||||
id={@id}
|
||||
type={@type}
|
||||
name={@name}
|
||||
value="true"
|
||||
checked={Phoenix.HTML.Form.normalize_value(@type, @value)}
|
||||
{@rest}
|
||||
/>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
def input(%{type: "textarea"} = assigns) do
|
||||
~H"""
|
||||
<div class={["flex flex-col", @class]}>
|
||||
|
|
|
@ -44,14 +44,23 @@
|
|||
<.markdown content={@status.body} />
|
||||
</div>
|
||||
|
||||
<footer class="mt-4 border-t border-gray-200 pt-2 text-sm text-gray-500">
|
||||
<%= if @status.published_at do %>
|
||||
<.timex
|
||||
value={Core.Posts.publish_date_time(@status)}
|
||||
format="{Mfull} {D}, {YYYY} at {h12}:{m} {AM}"
|
||||
class="dt-published"
|
||||
/>
|
||||
<% end %>
|
||||
<footer class="mt-4 border-t border-gray-200 pt-2 text-sm text-gray-500 flex flex-row justify-between">
|
||||
<div>
|
||||
<%= if @status.published_at do %>
|
||||
<.timex
|
||||
value={Core.Posts.publish_date_time(@status)}
|
||||
format="{Mfull} {D}, {YYYY} at {h12}:{m} {AM}"
|
||||
class="dt-published"
|
||||
/>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="flex flex-row gap-x-2">
|
||||
<%= if @status.mastodon_post do %>
|
||||
<.link href={@status.mastodon_post.url} target="_blank" class="u-syndication">
|
||||
tech.lgbt
|
||||
</.link>
|
||||
<% end %>
|
||||
</div>
|
||||
</footer>
|
||||
</article>
|
||||
</div>
|
||||
|
|
|
@ -25,6 +25,12 @@
|
|||
<.button phx-click="publish" class="self-end">publish</.button>
|
||||
<% end %>
|
||||
<.input type="datetime-local" label="deleted (utc)" field={@form[:deleted_at]} />
|
||||
<.input
|
||||
type="checkbox"
|
||||
label="syndicate to mastodon"
|
||||
class="self-end"
|
||||
field={@form[:syndicate_to_mastodon]}
|
||||
/>
|
||||
<%= if @post.deleted_at do %>
|
||||
<.button phx-click="undelete" class="self-end">undelete</.button>
|
||||
<% else %>
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
defmodule Core.Repo.Migrations.AddMastodonPostsTable do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:mastodon_posts, primary_key: false) do
|
||||
add :id, :uuid, primary_key: true
|
||||
add :post_id, references(:posts, type: :uuid, on_delete: :delete_all), null: false
|
||||
add :status_id, :text, null: false
|
||||
add :url, :text, null: false
|
||||
|
||||
timestamps(type: :utc_datetime_usec)
|
||||
end
|
||||
|
||||
create unique_index(:mastodon_posts, [:post_id])
|
||||
|
||||
alter table(:posts) do
|
||||
add :syndicate_to_mastodon, :boolean, default: false, null: false
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue