devel #117
6 changed files with 21 additions and 407 deletions
|
@ -1,5 +1,5 @@
|
||||||
defmodule ChiyaWeb.Indie.MicropubHandler do
|
defmodule ChiyaWeb.Indie.MicropubHandler do
|
||||||
@behaviour ChiyaWeb.Indie.PlugMicropub.HandlerBehaviour
|
@behaviour PlugMicropub.HandlerBehaviour
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
alias ChiyaWeb.Indie.Properties, as: Props
|
alias ChiyaWeb.Indie.Properties, as: Props
|
||||||
|
@ -75,12 +75,25 @@ defmodule ChiyaWeb.Indie.MicropubHandler do
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_syndicate_to_query(_access_token) do
|
def handle_syndicate_to_query(access_token) do
|
||||||
{:ok, %{"syndicate-to" => []}}
|
case verify_token(access_token) do
|
||||||
|
:ok -> {:ok, %{"syndicate-to" => []}}
|
||||||
|
_ -> {:error, :insufficient_scope}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_category_query(access_token) do
|
||||||
|
case verify_token(access_token) do
|
||||||
|
:ok ->
|
||||||
|
tags = Enum.map(Chiya.Tags.list_tags(), fn t -> t.name end)
|
||||||
|
{:ok, %{"categories" => tags}}
|
||||||
|
_ -> {:error, :insufficient_scope}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp verify_token(access_token) do
|
defp verify_token(access_token) do
|
||||||
Enum.reduce_while([&verify_app_token/1, &verify_micropub_token/1], nil, fn fun, result ->
|
Enum.reduce_while([&verify_app_token/1, &verify_micropub_token/1], nil, fn fun, _result ->
|
||||||
case fun.(access_token) do
|
case fun.(access_token) do
|
||||||
:ok -> {:halt, :ok}
|
:ok -> {:halt, :ok}
|
||||||
error -> {:cont, error}
|
error -> {:cont, error}
|
||||||
|
|
|
@ -1,343 +0,0 @@
|
||||||
defmodule ChiyaWeb.Indie.PlugMicropub do
|
|
||||||
@moduledoc """
|
|
||||||
A Plug for building a Micropub server.
|
|
||||||
|
|
||||||
To use:
|
|
||||||
|
|
||||||
"""
|
|
||||||
use Plug.Router
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
plug :match
|
|
||||||
plug :dispatch
|
|
||||||
|
|
||||||
# Plug Callbacks
|
|
||||||
|
|
||||||
@doc false
|
|
||||||
def init(opts) do
|
|
||||||
handler =
|
|
||||||
Keyword.get(opts, :handler) || raise ArgumentError, "Micropub Plug requires :handler option"
|
|
||||||
|
|
||||||
json_encoder =
|
|
||||||
Keyword.get(opts, :json_encoder) ||
|
|
||||||
raise ArgumentError, "Micropub Plug requires :json_encoder option"
|
|
||||||
|
|
||||||
[handler: handler, json_encoder: json_encoder]
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc false
|
|
||||||
def call(conn, opts) do
|
|
||||||
conn = put_private(conn, :plug_micropub, opts)
|
|
||||||
super(conn, opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Routes
|
|
||||||
|
|
||||||
post "/" do
|
|
||||||
with {:ok, access_token, conn} <- get_access_token(conn),
|
|
||||||
{:ok, action, conn} <- get_action(conn) do
|
|
||||||
handle_action(action, access_token, conn)
|
|
||||||
else
|
|
||||||
error -> send_error(conn, error)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
get "/" do
|
|
||||||
with {:ok, access_token, conn} <- get_access_token(conn),
|
|
||||||
{:ok, query} <- get_query(conn) do
|
|
||||||
handle_query(query, access_token, conn)
|
|
||||||
else
|
|
||||||
error -> send_error(conn, error)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
post "/media" do
|
|
||||||
handler = conn.private[:plug_micropub][:handler]
|
|
||||||
|
|
||||||
with {:ok, access_token, conn} <- get_access_token(conn),
|
|
||||||
{:ok, file} <- get_file(conn),
|
|
||||||
{:ok, url} <- handler.handle_media(file, access_token) do
|
|
||||||
conn
|
|
||||||
|> put_resp_header("location", url)
|
|
||||||
|> send_resp(:created, "")
|
|
||||||
else
|
|
||||||
error -> send_error(conn, error)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
match _ do
|
|
||||||
send_error(conn, {:error, :invalid_request})
|
|
||||||
end
|
|
||||||
|
|
||||||
# Internal Functions
|
|
||||||
|
|
||||||
defp send_content(conn, content) do
|
|
||||||
json_encoder = conn.private[:plug_micropub][:json_encoder]
|
|
||||||
body = json_encoder.encode!(content)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_resp_content_type("application/json")
|
|
||||||
|> send_resp(:ok, body)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp send_error(conn, {:error, error}) do
|
|
||||||
body = %{error: error}
|
|
||||||
_send_error(conn, body)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp send_error(conn, {:error, error, description}) do
|
|
||||||
body = %{error: error, error_description: description}
|
|
||||||
_send_error(conn, body)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp _send_error(conn, body) do
|
|
||||||
json_encoder = conn.private[:plug_micropub][:json_encoder]
|
|
||||||
|
|
||||||
code = get_error_code(body.error)
|
|
||||||
body = json_encoder.encode!(body)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_resp_content_type("application/json")
|
|
||||||
|> send_resp(code, body)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_error_code(:insufficient_scope), do: :unauthorized
|
|
||||||
defp get_error_code(:invalid_request), do: :bad_request
|
|
||||||
defp get_error_code(code), do: code
|
|
||||||
|
|
||||||
defp get_action(conn) do
|
|
||||||
{action, body_params} = Map.pop(conn.body_params, "action")
|
|
||||||
conn = %Plug.Conn{conn | body_params: body_params}
|
|
||||||
|
|
||||||
case action do
|
|
||||||
nil ->
|
|
||||||
Logger.info("Action: create")
|
|
||||||
{:ok, :create, conn}
|
|
||||||
|
|
||||||
action when action in ["delete", "undelete", "update"] ->
|
|
||||||
Logger.info("Action: #{action}")
|
|
||||||
{:ok, String.to_existing_atom(action), conn}
|
|
||||||
|
|
||||||
action ->
|
|
||||||
Logger.error("Invalid action: #{action}")
|
|
||||||
{:error, :invalid_request}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_query(conn) do
|
|
||||||
case Map.fetch(conn.query_params, "q") do
|
|
||||||
{:ok, query} when query in ["config", "source", "syndicate-to"] ->
|
|
||||||
Logger.info("Query: #{query}")
|
|
||||||
{:ok, String.to_existing_atom(query)}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
{:error, :invalid_request}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_file(conn) do
|
|
||||||
case Map.fetch(conn.body_params, "file") do
|
|
||||||
{:ok, file} -> {:ok, file}
|
|
||||||
:error -> {:error, :invalid_request}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_access_token(conn) do
|
|
||||||
{access_token, body_params} = Map.pop(conn.body_params, "access_token")
|
|
||||||
conn = %Plug.Conn{conn | body_params: body_params}
|
|
||||||
|
|
||||||
case access_token do
|
|
||||||
nil -> parse_auth_header(conn)
|
|
||||||
access_token -> {:ok, access_token, conn}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp parse_auth_header(conn) do
|
|
||||||
with [header] <- get_req_header(conn, "authorization"),
|
|
||||||
_ = IO.inspect(header),
|
|
||||||
"Bearer" <> token <- header,
|
|
||||||
do: {:ok, String.trim(token), conn},
|
|
||||||
else: (_ -> {:error, :unauthorized})
|
|
||||||
end
|
|
||||||
|
|
||||||
defp handle_action(:create, access_token, conn) do
|
|
||||||
content_type = conn |> get_req_header("content-type") |> List.first()
|
|
||||||
handler = conn.private[:plug_micropub][:handler]
|
|
||||||
|
|
||||||
Logger.info("Calling handle_action")
|
|
||||||
Logger.info("content type: #{content_type}")
|
|
||||||
|
|
||||||
with {:ok, type, properties} <- parse_create_body(content_type, conn.body_params),
|
|
||||||
{:ok, code, url} <- handler.handle_create(type, properties, access_token) do
|
|
||||||
conn
|
|
||||||
|> put_resp_header("location", url)
|
|
||||||
|> send_resp(code, "")
|
|
||||||
else
|
|
||||||
error -> send_error(conn, error)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp handle_action(:update, access_token, conn) do
|
|
||||||
content_type = conn |> get_req_header("content-type") |> List.first()
|
|
||||||
|
|
||||||
Logger.info("Calling handle_action")
|
|
||||||
Logger.info("content type: #{content_type}")
|
|
||||||
|
|
||||||
with "application/json" <- content_type,
|
|
||||||
{url, properties} when is_binary(url) <- Map.pop(conn.body_params, "url"),
|
|
||||||
{:ok, replace, add, delete} <- parse_update_properties(properties),
|
|
||||||
do: do_update(conn, access_token, url, replace, add, delete),
|
|
||||||
else: (_ -> send_error(conn, {:error, :invalid_request}))
|
|
||||||
end
|
|
||||||
|
|
||||||
defp handle_action(:delete, access_token, conn) do
|
|
||||||
Logger.info("Calling handle_action")
|
|
||||||
|
|
||||||
with {:ok, url} <- Map.fetch(conn.body_params, "url"),
|
|
||||||
do: do_delete(conn, access_token, url),
|
|
||||||
else: (_ -> send_error(conn, {:error, :invalid_request}))
|
|
||||||
end
|
|
||||||
|
|
||||||
defp handle_action(:undelete, access_token, conn) do
|
|
||||||
Logger.info("Calling handle_action")
|
|
||||||
|
|
||||||
with {:ok, url} <- Map.fetch(conn.body_params, "url"),
|
|
||||||
do: do_undelete(conn, access_token, url),
|
|
||||||
else: (_ -> send_error(conn, {:error, :invalid_request}))
|
|
||||||
end
|
|
||||||
|
|
||||||
defp handle_query(:config, access_token, conn) do
|
|
||||||
handler = conn.private[:plug_micropub][:handler]
|
|
||||||
|
|
||||||
case handler.handle_config_query(access_token) do
|
|
||||||
{:ok, content} -> send_content(conn, content)
|
|
||||||
error -> send_error(conn, error)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp handle_query(:source, access_token, conn) do
|
|
||||||
with {:ok, url} <- Map.fetch(conn.query_params, "url"),
|
|
||||||
do: do_source_query(conn, access_token, url),
|
|
||||||
else: (_ -> send_error(conn, {:error, :invalid_request}))
|
|
||||||
end
|
|
||||||
|
|
||||||
defp handle_query(:"syndicate-to", access_token, conn) do
|
|
||||||
handler = conn.private[:plug_micropub][:handler]
|
|
||||||
|
|
||||||
case handler.handle_syndicate_to_query(access_token) do
|
|
||||||
{:ok, content} -> send_content(conn, content)
|
|
||||||
error -> send_error(conn, error)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp parse_update_properties(properties) do
|
|
||||||
properties = Map.take(properties, ["replace", "add", "delete"])
|
|
||||||
|
|
||||||
valid? =
|
|
||||||
Enum.all?(properties, fn
|
|
||||||
{"delete", prop} when is_list(prop) ->
|
|
||||||
Enum.all?(prop, &is_binary/1)
|
|
||||||
|
|
||||||
{_k, prop} when is_map(prop) ->
|
|
||||||
Enum.all?(prop, fn
|
|
||||||
{_k, v} when is_list(v) -> true
|
|
||||||
_ -> false
|
|
||||||
end)
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
false
|
|
||||||
end)
|
|
||||||
|
|
||||||
if valid? do
|
|
||||||
replace = Map.get(properties, "replace", %{})
|
|
||||||
add = Map.get(properties, "add", %{})
|
|
||||||
delete = Map.get(properties, "delete", %{})
|
|
||||||
{:ok, replace, add, delete}
|
|
||||||
else
|
|
||||||
:error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_update(conn, access_token, url, replace, add, delete) do
|
|
||||||
handler = conn.private[:plug_micropub][:handler]
|
|
||||||
|
|
||||||
case handler.handle_update(url, replace, add, delete, access_token) do
|
|
||||||
:ok ->
|
|
||||||
send_resp(conn, :no_content, "")
|
|
||||||
|
|
||||||
{:ok, url} ->
|
|
||||||
conn
|
|
||||||
|> put_resp_header("location", url)
|
|
||||||
|> send_resp(:created, "")
|
|
||||||
|
|
||||||
error ->
|
|
||||||
send_error(conn, error)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_delete(conn, access_token, url) do
|
|
||||||
handler = conn.private[:plug_micropub][:handler]
|
|
||||||
|
|
||||||
case handler.handle_delete(url, access_token) do
|
|
||||||
:ok -> send_resp(conn, :no_content, "")
|
|
||||||
error -> send_error(conn, error)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_undelete(conn, access_token, url) do
|
|
||||||
handler = conn.private[:plug_micropub][:handler]
|
|
||||||
|
|
||||||
case handler.handle_undelete(url, access_token) do
|
|
||||||
:ok ->
|
|
||||||
send_resp(conn, :no_content, "")
|
|
||||||
|
|
||||||
{:ok, url} ->
|
|
||||||
conn
|
|
||||||
|> put_resp_header("location", url)
|
|
||||||
|> send_resp(:created, "")
|
|
||||||
|
|
||||||
error ->
|
|
||||||
send_error(conn, error)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_source_query(conn, access_token, url) do
|
|
||||||
handler = conn.private[:plug_micropub][:handler]
|
|
||||||
properties = Map.get(conn.query_params, "properties", [])
|
|
||||||
|
|
||||||
case handler.handle_source_query(url, properties, access_token) do
|
|
||||||
{:ok, content} -> send_content(conn, content)
|
|
||||||
error -> send_error(conn, error)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_create_body("application/json", params) do
|
|
||||||
Logger.info("Parsing body")
|
|
||||||
Logger.info("Params: #{inspect(params)}")
|
|
||||||
|
|
||||||
with {:ok, ["h-" <> type]} <- Map.fetch(params, "type"),
|
|
||||||
{:ok, properties} when is_map(properties) <- Map.fetch(params, "properties") do
|
|
||||||
properties = Map.new(properties)
|
|
||||||
|
|
||||||
{:ok, type, properties}
|
|
||||||
else
|
|
||||||
_ -> {:error, :invalid_request}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_create_body(_, params) do
|
|
||||||
Logger.info("Parsing body")
|
|
||||||
Logger.info("Params: #{inspect(params)}")
|
|
||||||
|
|
||||||
with {type, params} when is_binary(type) <- Map.pop(params, "h") do
|
|
||||||
properties =
|
|
||||||
params
|
|
||||||
|> Enum.map(fn {k, v} -> {k, List.wrap(v)} end)
|
|
||||||
|> Map.new()
|
|
||||||
|
|
||||||
{:ok, type, properties}
|
|
||||||
else
|
|
||||||
_ -> {:error, :invalid_request}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,57 +0,0 @@
|
||||||
defmodule ChiyaWeb.Indie.PlugMicropub.HandlerBehaviour do
|
|
||||||
@moduledoc """
|
|
||||||
Behaviour defining the interface for a PlugMicropub Handler
|
|
||||||
"""
|
|
||||||
|
|
||||||
@type access_token :: String.t()
|
|
||||||
@type handler_error_atom :: :invalid_request | :forbidden | :insufficient_scope
|
|
||||||
@type handler_error ::
|
|
||||||
{:error, handler_error_atom} | {:error, handler_error_atom, description :: String.t()}
|
|
||||||
|
|
||||||
@callback handle_create(type :: String.t(), properties :: map, access_token) ::
|
|
||||||
{:ok, :created | :accepted, url :: String.t()}
|
|
||||||
| handler_error
|
|
||||||
|
|
||||||
@callback handle_update(
|
|
||||||
url :: String.t(),
|
|
||||||
replace :: map,
|
|
||||||
add :: map,
|
|
||||||
delete :: map,
|
|
||||||
access_token
|
|
||||||
) ::
|
|
||||||
:ok
|
|
||||||
| {:ok, url :: String.t()}
|
|
||||||
| handler_error
|
|
||||||
|
|
||||||
@callback handle_delete(url :: String.t(), access_token) ::
|
|
||||||
:ok
|
|
||||||
| handler_error
|
|
||||||
|
|
||||||
@callback handle_undelete(url :: String.t(), access_token) ::
|
|
||||||
:ok
|
|
||||||
| {:ok, url :: String.t()}
|
|
||||||
| handler_error
|
|
||||||
|
|
||||||
@callback handle_config_query(access_token) ::
|
|
||||||
{:ok, map}
|
|
||||||
| handler_error
|
|
||||||
|
|
||||||
@callback handle_config_query(access_token) ::
|
|
||||||
{:ok, map}
|
|
||||||
| handler_error
|
|
||||||
|
|
||||||
@callback handle_syndicate_to_query(access_token) ::
|
|
||||||
{:ok, map}
|
|
||||||
| handler_error
|
|
||||||
|
|
||||||
@callback handle_source_query(
|
|
||||||
url :: String.t(),
|
|
||||||
properties :: [String.t()],
|
|
||||||
access_token
|
|
||||||
) ::
|
|
||||||
{:ok, map}
|
|
||||||
| handler_error
|
|
||||||
|
|
||||||
@callback handle_media(file :: Plug.Upload.t(), access_token) ::
|
|
||||||
{:ok, url :: String.t()} | handler_error
|
|
||||||
end
|
|
|
@ -39,8 +39,8 @@ defmodule ChiyaWeb.Router do
|
||||||
## Indie routes
|
## Indie routes
|
||||||
scope "/indie" do
|
scope "/indie" do
|
||||||
forward "/micropub",
|
forward "/micropub",
|
||||||
ChiyaWeb.Indie.PlugMicropub,
|
PlugMicropub,
|
||||||
handler: ChiyaWeb.Indie.MicropubHandler,
|
handler: MicropubHandler,
|
||||||
json_encoder: Jason
|
json_encoder: Jason
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
1
mix.exs
1
mix.exs
|
@ -49,6 +49,7 @@ defmodule Chiya.MixProject do
|
||||||
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
||||||
{:phoenix_live_view, "~> 0.19"},
|
{:phoenix_live_view, "~> 0.19"},
|
||||||
{:plug_cowboy, "~> 2.5"},
|
{:plug_cowboy, "~> 2.5"},
|
||||||
|
{:plug_micropub, path: "../plug_micropub"},
|
||||||
{:postgrex, ">= 0.0.0"},
|
{:postgrex, ">= 0.0.0"},
|
||||||
{:swoosh, "~> 1.3"},
|
{:swoosh, "~> 1.3"},
|
||||||
{:tailwind, "~> 0.2.0", runtime: Mix.env() == :dev},
|
{:tailwind, "~> 0.2.0", runtime: Mix.env() == :dev},
|
||||||
|
|
2
mix.lock
2
mix.lock
|
@ -46,7 +46,7 @@
|
||||||
"plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
|
"plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
|
||||||
"plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
|
"plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
|
||||||
"plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
|
"plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
|
||||||
"plug_micropub": {:git, "https://git.inhji.de/inhji/plug_micropub", "874eed58d73daaef48302b63dd59d61f3527dad9", [ref: "main"]},
|
"plug_micropub": {:git, "https://git.inhji.de/inhji/plug_micropub.git", "874eed58d73daaef48302b63dd59d61f3527dad9", [ref: "main"]},
|
||||||
"postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"},
|
"postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"},
|
||||||
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
||||||
"slugger": {:hex, :slugger, "0.3.0", "efc667ab99eee19a48913ccf3d038b1fb9f165fa4fbf093be898b8099e61b6ed", [:mix], [], "hexpm", "20d0ded0e712605d1eae6c5b4889581c3460d92623a930ddda91e0e609b5afba"},
|
"slugger": {:hex, :slugger, "0.3.0", "efc667ab99eee19a48913ccf3d038b1fb9f165fa4fbf093be898b8099e61b6ed", [:mix], [], "hexpm", "20d0ded0e712605d1eae6c5b4889581c3460d92623a930ddda91e0e609b5afba"},
|
||||||
|
|
Loading…
Reference in a new issue