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
|
config :tesla, adapter: Tesla.Adapter.Mint
|
||||||
|
|
||||||
|
mastodon_instance = "https://tech.lgbt"
|
||||||
|
|
||||||
|
config :sloanely_but_surely, Core.Syndication, mastodon_instance: mastodon_instance
|
||||||
|
|
||||||
config :ueberauth, Ueberauth,
|
config :ueberauth, Ueberauth,
|
||||||
providers: [
|
providers: [
|
||||||
mastodon:
|
mastodon:
|
||||||
{Ueberauth.Strategy.Mastodon,
|
{Ueberauth.Strategy.Mastodon,
|
||||||
[
|
[
|
||||||
instance: "https://tech.lgbt",
|
instance: mastodon_instance,
|
||||||
client_id: {System, :get_env, ["MASTODON_CLIENT_ID"]},
|
client_id: {System, :get_env, ["MASTODON_CLIENT_ID"]},
|
||||||
client_secret: {System, :get_env, ["MASTODON_CLIENT_SECRET"]},
|
client_secret: {System, :get_env, ["MASTODON_CLIENT_SECRET"]},
|
||||||
scope: "read write push"
|
scope: "read write push"
|
||||||
|
|
|
@ -85,9 +85,14 @@ defmodule Core.Posts do
|
||||||
end
|
end
|
||||||
|
|
||||||
def publish_post(%Schema.Post{} = post, published_at \\ DateTime.utc_now()) do
|
def publish_post(%Schema.Post{} = post, published_at \\ DateTime.utc_now()) do
|
||||||
post
|
with {:ok, post} <-
|
||||||
|> Post.publish_changeset(published_at)
|
post
|
||||||
|> Core.Repo.update()
|
|> Post.publish_changeset(published_at)
|
||||||
|
|> Core.Repo.update() do
|
||||||
|
Core.Syndication.syndicate_to_mastodon(post)
|
||||||
|
|
||||||
|
{:ok, post}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_post(%Schema.Post{} = post, deleted_at \\ DateTime.utc_now()) do
|
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
|
def content_changeset(%Schema.Post{} = post, attrs) do
|
||||||
changeset =
|
changeset =
|
||||||
post
|
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([:kind], message: "must have a kind")
|
||||||
|> validate_required([:body], message: "must have a body")
|
|> validate_required([:body], message: "must have a body")
|
||||||
|
|
||||||
|
@ -77,7 +86,11 @@ defmodule Core.Posts.Post do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
def base do
|
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
|
end
|
||||||
|
|
||||||
def current(query \\ base()) do
|
def current(query \\ base()) do
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
defmodule Core.Syndication do
|
defmodule Core.Syndication do
|
||||||
alias __MODULE__
|
alias __MODULE__
|
||||||
|
|
||||||
|
@mastodon_instance Application.compile_env!(:sloanely_but_surely, [
|
||||||
|
Core.Syndication,
|
||||||
|
:mastodon_instance
|
||||||
|
])
|
||||||
|
|
||||||
def get_mastodon_account(user) do
|
def get_mastodon_account(user) do
|
||||||
Core.Repo.get_by(Schema.MastodonAccount, user_id: user.id)
|
Core.Repo.get_by(Schema.MastodonAccount, user_id: user.id)
|
||||||
end
|
end
|
||||||
|
@ -11,4 +16,38 @@ defmodule Core.Syndication do
|
||||||
|> Syndication.MastodonAccount.changeset(attrs)
|
|> Syndication.MastodonAccount.changeset(attrs)
|
||||||
|> Core.Repo.insert()
|
|> Core.Repo.insert()
|
||||||
end
|
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
|
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 :published_at, :utc_datetime_usec
|
||||||
field :deleted_at, :utc_datetime_usec
|
field :deleted_at, :utc_datetime_usec
|
||||||
|
|
||||||
|
field :syndicate_to_mastodon, :boolean
|
||||||
|
|
||||||
|
has_one :mastodon_post, Schema.MastodonPost
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ defmodule Web.CoreComponents do
|
||||||
attr :label, :string, default: nil
|
attr :label, :string, default: nil
|
||||||
attr :value, :any
|
attr :value, :any
|
||||||
attr :class, :string, default: nil
|
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 :field, FormField
|
||||||
attr :errors, :list, default: []
|
attr :errors, :list, default: []
|
||||||
attr :rest, :global, include: ~w[disabled form pattern placeholder readonly required]
|
attr :rest, :global, include: ~w[disabled form pattern placeholder readonly required]
|
||||||
|
@ -35,6 +35,23 @@ defmodule Web.CoreComponents do
|
||||||
|> input()
|
|> input()
|
||||||
end
|
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
|
def input(%{type: "textarea"} = assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class={["flex flex-col", @class]}>
|
<div class={["flex flex-col", @class]}>
|
||||||
|
|
|
@ -44,14 +44,23 @@
|
||||||
<.markdown content={@status.body} />
|
<.markdown content={@status.body} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="mt-4 border-t border-gray-200 pt-2 text-sm text-gray-500">
|
<footer class="mt-4 border-t border-gray-200 pt-2 text-sm text-gray-500 flex flex-row justify-between">
|
||||||
<%= if @status.published_at do %>
|
<div>
|
||||||
<.timex
|
<%= if @status.published_at do %>
|
||||||
value={Core.Posts.publish_date_time(@status)}
|
<.timex
|
||||||
format="{Mfull} {D}, {YYYY} at {h12}:{m} {AM}"
|
value={Core.Posts.publish_date_time(@status)}
|
||||||
class="dt-published"
|
format="{Mfull} {D}, {YYYY} at {h12}:{m} {AM}"
|
||||||
/>
|
class="dt-published"
|
||||||
<% end %>
|
/>
|
||||||
|
<% 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>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,6 +25,12 @@
|
||||||
<.button phx-click="publish" class="self-end">publish</.button>
|
<.button phx-click="publish" class="self-end">publish</.button>
|
||||||
<% end %>
|
<% end %>
|
||||||
<.input type="datetime-local" label="deleted (utc)" field={@form[:deleted_at]} />
|
<.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 %>
|
<%= if @post.deleted_at do %>
|
||||||
<.button phx-click="undelete" class="self-end">undelete</.button>
|
<.button phx-click="undelete" class="self-end">undelete</.button>
|
||||||
<% else %>
|
<% 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