feat: Federate posts
This commit is contained in:
parent
87c5ce66cd
commit
47b5313293
7 changed files with 113 additions and 24 deletions
|
|
@ -1,6 +1,8 @@
|
|||
defmodule ActivityPub do
|
||||
alias ActivityPub.Headers
|
||||
|
||||
def get(url, actor_url, private_key) do
|
||||
headers = ActivityPub.Headers.signing_headers("GET", url, "", actor_url, private_key)
|
||||
headers = Headers.signing_headers("GET", url, "", actor_url, private_key)
|
||||
|
||||
Req.new(url: url)
|
||||
|> Req.Request.put_header("accept", "application/activity+json")
|
||||
|
|
@ -15,6 +17,21 @@ defmodule ActivityPub do
|
|||
end
|
||||
end
|
||||
|
||||
def post_activity(sender_id, private_key, inbox, activity) do
|
||||
body = Jason.encode!(activity)
|
||||
|
||||
headers =
|
||||
Headers.signing_headers(
|
||||
"POST",
|
||||
inbox,
|
||||
body,
|
||||
sender_id,
|
||||
private_key
|
||||
)
|
||||
|
||||
Req.post(inbox, headers: headers, body: body)
|
||||
end
|
||||
|
||||
def fetch_actor(actor_id) do
|
||||
request =
|
||||
Req.new(url: actor_id)
|
||||
|
|
|
|||
|
|
@ -13,38 +13,72 @@ defmodule Postland.Activities do
|
|||
id = Ecto.UUID.autogenerate()
|
||||
html = Earmark.as_html!(markdown)
|
||||
|
||||
%{
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => url(~p"/activities/#{id}"),
|
||||
"type" => "Create",
|
||||
"actor" => Postland.my_actor_id(),
|
||||
"object" => [
|
||||
%{
|
||||
activity =
|
||||
%{
|
||||
"@context" => [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
%{
|
||||
"ostatus" => "http://ostatus.org#",
|
||||
"atomUri" => "ostatus:atomUri",
|
||||
"inReplyToAtomUri" => "ostatus:inReplyToAtomUri",
|
||||
"conversation" => "ostatus:conversation",
|
||||
"sensitive" => "as:sensitive",
|
||||
"toot" => "http://joinmastodon.org/ns#",
|
||||
"votersCount" => "toot:votersCount"
|
||||
}
|
||||
],
|
||||
"id" => url(~p"/activities/#{id}"),
|
||||
"type" => "Create",
|
||||
"actor" => Postland.my_actor_id(),
|
||||
"object" => %{
|
||||
"id" => url(~p"/activities/#{id}"),
|
||||
"type" => "Note",
|
||||
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||
"attributedTo" => Postland.my_actor_id(),
|
||||
"mediaType" => "text/html",
|
||||
"content" => html,
|
||||
"to" => "https://www.w3.org/ns/activitystreams#Public"
|
||||
},
|
||||
%{
|
||||
"id" => url(~p"/activities/#{id}"),
|
||||
"type" => "Note",
|
||||
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||
"attributedTo" => Postland.my_actor_id(),
|
||||
"mediaType" => "text/markdown",
|
||||
"content" => markdown,
|
||||
"to" => "https://www.w3.org/ns/activitystreams#Public"
|
||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"cc" => [
|
||||
"#{Postland.my_actor_id()}/followers"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
activity
|
||||
|> Postland.Activity.changeset()
|
||||
|> record_create_activity()
|
||||
|
||||
# TODO: ActivityPub out this activity (only for our own posts obviously)
|
||||
|> broadcast_to_followers(activity)
|
||||
end
|
||||
|
||||
def broadcast_to_followers({:ok, _} = result, activity) do
|
||||
my_actor_id = Postland.my_actor_id()
|
||||
private_key = Postland.Accounts.solo_user().private_key
|
||||
|
||||
Postland.Follows.all_followers()
|
||||
|> Enum.map(fn %{follower: follower} ->
|
||||
inbox = Postland.Actors.inbox(follower)
|
||||
dbg(follower)
|
||||
dbg(inbox)
|
||||
|
||||
case inbox do
|
||||
nil ->
|
||||
:ok
|
||||
|
||||
inbox ->
|
||||
ActivityPub.post_activity(
|
||||
my_actor_id,
|
||||
private_key,
|
||||
inbox,
|
||||
activity
|
||||
)
|
||||
end
|
||||
end)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def broadcast_to_followers(result, _activity), do: result
|
||||
|
||||
def record_create_activity(changeset) do
|
||||
Multi.new()
|
||||
|> Multi.insert(:activity, changeset)
|
||||
|
|
|
|||
22
lib/postland/actors.ex
Normal file
22
lib/postland/actors.ex
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
defmodule Postland.Actors do
|
||||
def inbox(actor_id) do
|
||||
Map.get(actor(actor_id), "inbox")
|
||||
end
|
||||
|
||||
def actor(actor_id) do
|
||||
result =
|
||||
Cachex.fetch(:main_cache, "actor:#{actor_id}", fn _key ->
|
||||
{:ok, actor} = ActivityPub.fetch_actor(actor_id)
|
||||
|
||||
{:commit, actor, expire: :timer.seconds(300)}
|
||||
end)
|
||||
|
||||
case result do
|
||||
{:commit, actor} ->
|
||||
actor
|
||||
|
||||
{:ok, actor} ->
|
||||
actor
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -14,6 +14,7 @@ defmodule Postland.Application do
|
|||
repos: Application.fetch_env!(:postland, :ecto_repos), skip: skip_migrations?()},
|
||||
{DNSCluster, query: Application.get_env(:postland, :dns_cluster_query) || :ignore},
|
||||
{Phoenix.PubSub, name: Postland.PubSub},
|
||||
{Cachex, [:main_cache]},
|
||||
# Start a worker by calling: Postland.Worker.start_link(arg)
|
||||
# {Postland.Worker, arg},
|
||||
# Start to serve requests, typically the last entry
|
||||
|
|
|
|||
|
|
@ -27,7 +27,15 @@ defmodule Postland.Follows do
|
|||
|
||||
def record_and_send_acceptance(request) do
|
||||
Multi.new()
|
||||
|> Multi.run(:confirm_timestamp, fn _data, _repo -> confirm_request(request) end)
|
||||
|> Multi.run(:confirm_timestamp, fn _data, _repo ->
|
||||
case confirm_request(request) do
|
||||
:ok ->
|
||||
{:ok, :ok}
|
||||
|
||||
other ->
|
||||
{:error, other}
|
||||
end
|
||||
end)
|
||||
|> Multi.run(:send_acceptance, fn _data, _repo ->
|
||||
send_acceptance(request)
|
||||
end)
|
||||
|
|
|
|||
3
mix.exs
3
mix.exs
|
|
@ -60,7 +60,8 @@ defmodule Postland.MixProject do
|
|||
{:bandit, "~> 1.2"},
|
||||
{:req, "~> 0.5.6"},
|
||||
{:stream_data, "~> 1.1.1", only: [:test]},
|
||||
{:earmark, "~> 1.4.47"}
|
||||
{:earmark, "~> 1.4.47"},
|
||||
{:cachex, "~> 4.0"}
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
|||
6
mix.lock
6
mix.lock
|
|
@ -1,6 +1,7 @@
|
|||
%{
|
||||
"bandit": {:hex, :bandit, "1.5.7", "6856b1e1df4f2b0cb3df1377eab7891bec2da6a7fd69dc78594ad3e152363a50", [:mix], [{:hpax, "~> 1.0.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f2dd92ae87d2cbea2fa9aa1652db157b6cba6c405cb44d4f6dd87abba41371cd"},
|
||||
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.1.0", "0b110a9a6c619b19a7f73fa3004aa11d6e719a67e672d1633dc36b6b2290a0f7", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2ad2acb5a8bc049e8d5aa267802631912bb80d5f4110a178ae7999e69dca1bf7"},
|
||||
"cachex": {:hex, :cachex, "4.0.2", "120f9c27b0a453c7cb3319d9dc6c61c050a480e5299fc1f8bded1e2e334992ab", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:ex_hash_ring, "~> 6.0", [hex: :ex_hash_ring, 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", "4f4890122bddd979f6c217d5e300d0c0d3eb858a976cbe1f65a94e6322bc5825"},
|
||||
"castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"},
|
||||
"cc_precompiler": {:hex, :cc_precompiler, "0.1.10", "47c9c08d8869cf09b41da36538f62bc1abd3e19e41701c2cea2675b53c704258", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f6e046254e53cd6b41c6bacd70ae728011aa82b2742a80d6e2214855c6e06b22"},
|
||||
"comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"},
|
||||
|
|
@ -13,6 +14,8 @@
|
|||
"ecto_sqlite3": {:hex, :ecto_sqlite3, "0.17.2", "200226e057f76c40be55fbac77771eb1a233260ab8ec7283f5da6d9402bde8de", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.12", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.22", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "a3838919c5a34c268c28cafab87b910bcda354a9a4e778658da46c149bb2c1da"},
|
||||
"elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"},
|
||||
"esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"},
|
||||
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
|
||||
"ex_hash_ring": {:hex, :ex_hash_ring, "6.0.4", "bef9d2d796afbbe25ab5b5a7ed746e06b99c76604f558113c273466d52fa6d6b", [:mix], [], "hexpm", "89adabf31f7d3dfaa36802ce598ce918e9b5b33bae8909ac1a4d052e1e567d18"},
|
||||
"expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"},
|
||||
"exqlite": {:hex, :exqlite, "0.24.1", "b15f6c7ca14e75b14637339bdd50d96ce6dc94ea4c6a133f94037d2379373f91", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "415e36e3f8d6ae9fb51327f481b0ce8858414abf838b565cf164469596762cb0"},
|
||||
"file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
|
||||
|
|
@ -22,6 +25,7 @@
|
|||
"heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized"]},
|
||||
"hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"},
|
||||
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
|
||||
"jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"},
|
||||
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
|
||||
"mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"},
|
||||
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
|
||||
|
|
@ -37,6 +41,7 @@
|
|||
"plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
|
||||
"req": {:hex, :req, "0.5.6", "8fe1eead4a085510fe3d51ad854ca8f20a622aae46e97b302f499dfb84f726ac", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cfaa8e720945d46654853de39d368f40362c2641c4b2153c886418914b372185"},
|
||||
"sleeplocks": {:hex, :sleeplocks, "1.1.3", "96a86460cc33b435c7310dbd27ec82ca2c1f24ae38e34f8edde97f756503441a", [:rebar3], [], "hexpm", "d3b3958552e6eb16f463921e70ae7c767519ef8f5be46d7696cc1ed649421321"},
|
||||
"stream_data": {:hex, :stream_data, "1.1.1", "fd515ca95619cca83ba08b20f5e814aaf1e5ebff114659dc9731f966c9226246", [:mix], [], "hexpm", "45d0cd46bd06738463fd53f22b70042dbb58c384bb99ef4e7576e7bb7d3b8c8c"},
|
||||
"swoosh": {:hex, :swoosh, "1.17.1", "01295a82bddd2c6cac1e65856e29444d7c23c4501e0ebc69cea8a82018227e25", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3b20d25e580cb79af631335a1bdcfbffd835c08ebcdc16e98577223a241a18a1"},
|
||||
"tailwind": {:hex, :tailwind, "0.2.3", "277f08145d407de49650d0a4685dc062174bdd1ae7731c5f1da86163a24dfcdb", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "8e45e7a34a676a7747d04f7913a96c770c85e6be810a1d7f91e713d3a3655b5d"},
|
||||
|
|
@ -44,6 +49,7 @@
|
|||
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"},
|
||||
"telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
|
||||
"thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"},
|
||||
"unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"},
|
||||
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
|
||||
"websock_adapter": {:hex, :websock_adapter, "0.5.7", "65fa74042530064ef0570b75b43f5c49bb8b235d6515671b3d250022cb8a1f9e", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "d0f478ee64deddfec64b800673fd6e0c8888b079d9f3444dd96d2a98383bdbd1"},
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue