Packages

Standalone command-line interface for hex.pm

Current section

Files

Jump to
hexer src hexer_server.erl
Raw

src/hexer_server.erl

%%% @doc Connection to hex.pm server
-module(hexer_server).
-export([ new_api_key/3
, create_user/3
, publish_package/3
, publish_docs/4
]).
%%------------------------------------------------------------------------------
%% API
%%------------------------------------------------------------------------------
%% @doc Generates a new API key
-spec new_api_key(binary(), string(), string()) ->
{ok, binary()} | {error, any()}.
new_api_key(Name, Username, Password) ->
Body = term_to_binary(#{<<"name">> => Name}),
case post(Username, Password, "/api/keys", Body) of
{ok, #{status_code := StatusCode, body:= ResBody}}
when 200 =< StatusCode, StatusCode < 300 ->
#{<<"secret">> := Secret} = binary_to_term(ResBody),
{ok, Secret};
{ok, #{status_code := StatusCode, body := ResBody}} ->
{error, {StatusCode, binary_to_term(ResBody)}};
{error, Error} ->
{error, Error}
end.
%% @doc Creates a new user
-spec create_user(string(), string(), string()) -> ok | {error, any()}.
create_user(Username, Email, Password) ->
Body = term_to_binary(#{ <<"username">> => list_to_binary(Username)
, <<"email">> => list_to_binary(Email)
, <<"password">> => list_to_binary(Password)
}
),
case post(Username, Password, "/api/users", Body) of
{ok, #{status_code := StatusCode}}
when 200 =< StatusCode, StatusCode < 300 ->
ok;
{ok, #{status_code := StatusCode, body := ResBody}} ->
{error, {StatusCode, binary_to_term(ResBody)}};
{error, Error} ->
{error, Error}
end.
%% @doc Publishes a package
-spec publish_package(string(), string(), binary()) -> ok | {error, any()}.
publish_package(APIKey, Name, Tar) ->
Path = string:join(["/api/packages", Name, "releases"], "/"),
publish(APIKey, Path, Tar).
%% @doc Publishes the documentation for an app
-spec publish_docs(string(), string(), string(), binary()) ->
ok | {error, any()}.
publish_docs(APIKey, Name, Version, Tar) ->
Path =
string:join(["/api/packages", Name , "releases", Version , "docs"], "/"),
publish(APIKey, Path, Tar).
-spec publish(string(), string(), binary()) -> ok | {error, any()}.
publish(APIKey, Path, Tar) ->
case post(APIKey, Path, Tar) of
{ok, #{status_code := StatusCode}}
when 200 =< StatusCode, StatusCode < 300 ->
ok;
{ok, #{status_code := StatusCode, body := ResBody}} ->
{error, {StatusCode, binary_to_term(ResBody)}};
{error, Error} ->
{error, Error}
end.
%%------------------------------------------------------------------------------
%% Internal Functions
%%------------------------------------------------------------------------------
-spec open_connection() -> {ok, shotgun:connection()} | {error, any()}.
open_connection() ->
{Url, Port} = connection_info(),
shotgun:open(Url, Port, https).
connection_info() ->
{api_url(), api_port()}.
-spec api_url() -> string().
api_url() -> "hex.pm".
-spec api_port() -> non_neg_integer().
api_port() -> 443.
-spec post(string(), string(), string(), binary()) ->
{ok, map()} | {error, any()}.
post(Username, Password, Path, Body) ->
{ok, Conn} = open_connection(),
try
Headers = #{ basic_auth => {Username, Password}
, <<"Accept">> => <<"application/vnd.hex+erlang">>
, <<"Content-Type">> => <<"application/vnd.hex+erlang">>
},
shotgun:post(Conn, Path, Headers, Body, #{})
after
shotgun:close(Conn)
end.
-spec post(string(), string(), binary()) ->
{ok, map()} | {error, any()}.
post(APIKey, Path, Body) ->
{ok, Conn} = open_connection(),
try
Headers = #{ <<"Authorization">> => list_to_binary(APIKey)
, <<"Accept">> => <<"application/vnd.hex+erlang">>
, <<"Content-Type">> => <<"application/octet-stream">>
},
shotgun:post(Conn, Path, Headers, Body, #{timeout => 10000})
after
shotgun:close(Conn)
end.