diff --git a/.env.example b/.env.example
index 80e368a7..7736ad50 100644
--- a/.env.example
+++ b/.env.example
@@ -5,6 +5,7 @@ export BASIC_AUTH_PASSWORD=
export BASIC_AUTH_USERNAME=
export CANONICAL_DOMAIN=https://example.com
export DATE_DISPLAY_TZ=America/Chicago
+export DEFAULT_BLUESKY_HANDLE=
export DEFAULT_TWITTER_HANDLE=
export ENABLE_BASIC_AUTH=
export GOOGLE_CLIENT_ID=
@@ -18,3 +19,5 @@ export twitter_access_token=
export twitter_access_token_secret=
export twitter_consumer_key=
export twitter_consumer_secret=
+export bluesky_username=
+export bluesky_password=
diff --git a/config/config.exs b/config/config.exs
index 56658113..33042ee8 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -58,6 +58,7 @@ config :tilex, :twitter_notifier, Tilex.Notifications.Notifiers.Twitter
config :tilex, :organization_name, System.get_env("ORGANIZATION_NAME")
config :tilex, :canonical_domain, System.get_env("CANONICAL_DOMAIN")
config :tilex, :default_twitter_handle, System.get_env("DEFAULT_TWITTER_HANDLE")
+config :tilex, :default_bluesky_handle, System.get_env("DEFAULT_BLUESKY_HANDLE")
config :tilex, :cors_origin, System.get_env("CORS_ORIGIN")
config :tilex, :hosted_domain, System.get_env("HOSTED_DOMAIN")
config :tilex, :guest_author_allowlist, System.get_env("GUEST_AUTHOR_ALLOWLIST")
@@ -114,6 +115,10 @@ config :tilex, Tilex.Notifications.Notifiers.Twitter,
token: System.get_env("twitter_access_token"),
token_secret: System.get_env("twitter_access_token_secret")
+config :tilex, Tilex.Notifications.Notifiers.Bluesky,
+ username: System.get_env("bluesky_username"),
+ password: System.get_env("bluesky_password")
+
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"
diff --git a/lib/test/notifications/notifiers/bluesky.ex b/lib/test/notifications/notifiers/bluesky.ex
new file mode 100644
index 00000000..569bf7f1
--- /dev/null
+++ b/lib/test/notifications/notifiers/bluesky.ex
@@ -0,0 +1,15 @@
+defmodule Test.Notifications.Notifiers.Bluesky do
+ use Tilex.Notifications.Notifier
+
+ def handle_post_created(_post, _developer, _channel, _url) do
+ :ok
+ end
+
+ def handle_post_liked(_post, _developer, _url) do
+ :ok
+ end
+
+ def handle_page_views_report(_report) do
+ :ok
+ end
+end
diff --git a/lib/tilex/notifications/notifiers/bluesky.ex b/lib/tilex/notifications/notifiers/bluesky.ex
new file mode 100644
index 00000000..4b63353d
--- /dev/null
+++ b/lib/tilex/notifications/notifiers/bluesky.ex
@@ -0,0 +1,28 @@
+defmodule Tilex.Notifications.Notifiers.Bluesky do
+ alias BlueskyEx.Client
+ alias Tilex.Blog.Developer
+
+ use Tilex.Notifications.Notifier
+
+ def handle_post_created(post, developer, channel, url) do
+ "#{post.title} #{url} via @#{Developer.twitter_handle(developer)} #til ##{channel.twitter_hashtag}"
+ |> create_post()
+ end
+
+ def handle_post_liked(_post, _dev, _url) do
+ :ok
+ end
+
+ def handle_page_views_report(_report) do
+ :ok
+ end
+
+ def create_post(text) do
+ Client.Credentials
+ |> struct(credentials())
+ |> Client.Session.create("https://bsky.social")
+ |> Client.RecordManager.create_post(text: text)
+ end
+
+ defp credentials, do: Application.get_env(:tilex, __MODULE__)
+end
diff --git a/lib/tilex_web/endpoint.ex b/lib/tilex_web/endpoint.ex
index 04408c5d..2216ae73 100644
--- a/lib/tilex_web/endpoint.ex
+++ b/lib/tilex_web/endpoint.ex
@@ -1,6 +1,5 @@
defmodule TilexWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :tilex
- use Appsignal.Phoenix
if sandbox = Application.compile_env(:tilex, :sandbox) do
plug(Phoenix.Ecto.SQL.Sandbox, sandbox: sandbox)
diff --git a/lib/tilex_web/templates/layout/root.html.heex b/lib/tilex_web/templates/layout/root.html.heex
index cd7c3cb0..9b4261c4 100644
--- a/lib/tilex_web/templates/layout/root.html.heex
+++ b/lib/tilex_web/templates/layout/root.html.heex
@@ -92,6 +92,10 @@
<%= icon("twitter", :small) %>
Follow on Twitter
+
+ <%= icon("bluesky", :small) %>
+ Follow on Bluesky
+
diff --git a/mix.exs b/mix.exs
index 30d13321..27c25a5a 100644
--- a/mix.exs
+++ b/mix.exs
@@ -34,6 +34,7 @@ defmodule Tilex.Mixfile do
defp deps do
[
{:appsignal_phoenix, "~> 2.0"},
+ {:bluesky_ex, "~> 0.1.6"},
{:cachex, "~> 3.1"},
{:cors_plug, "~> 3.0"},
{:credo, "~> 1.6", only: [:dev, :test], runtime: false},
diff --git a/mix.lock b/mix.lock
index 5aed5a52..5810dd03 100644
--- a/mix.lock
+++ b/mix.lock
@@ -2,6 +2,7 @@
"appsignal": {:hex, :appsignal, "2.12.3", "de6aff6241bf7f1b5c6e058d976d3dcf00071613f7800f66049d9a77a3aa15d8", [:make, :mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:decorator, "~> 1.2.3 or ~> 1.3", [hex: :decorator, repo: "hexpm", optional: false]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2744685894960b5b0421e77240d1a8ccc1ff3a256344f42055af892efe6d1f06"},
"appsignal_phoenix": {:hex, :appsignal_phoenix, "2.5.0", "b0f5855d02d1f522f6029e71d3b11ebb1d37df63b562e380750841de25d35a7c", [:mix], [{:appsignal, ">= 2.11.0 and < 3.0.0", [hex: :appsignal, repo: "hexpm", optional: false]}, {:appsignal_plug, ">= 2.0.15 and < 3.0.0", [hex: :appsignal_plug, repo: "hexpm", optional: false]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.11 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.9 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01fe404add5fe32b837f6cf922cbc7f0047a8abbc78f1e943268cd48e0ed3169"},
"appsignal_plug": {:hex, :appsignal_plug, "2.0.15", "758a8a78944878e8461bbc77ca86219121a56f4299c6d79940ab083cf9afea00", [:mix], [{:appsignal, ">= 2.7.6 and < 3.0.0", [hex: :appsignal, repo: "hexpm", optional: false]}, {:plug, ">= 1.1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1c6059049e2081e808aaef04e2b9917e06277f61a35a0e103db860d08cbc41f1"},
+ "bluesky_ex": {:hex, :bluesky_ex, "0.1.6", "a4c6a8ecaac6d63f91877aee829f251becb27b376234967f566db5c093550dea", [:make, :mix], [{:httpoison, "~> 2.1", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.3", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "54b591093a4fe0dd9db63e867a3bdf9fd2fa03c091b71150bf72d0a100b62198"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"},
"castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"},
diff --git a/priv/static/images/icons.svg b/priv/static/images/icons.svg
index 3a9b0056..ddde9c9f 100644
--- a/priv/static/images/icons.svg
+++ b/priv/static/images/icons.svg
@@ -31,4 +31,7 @@
+