defmodule Immudb.Client do
use Immudb.Grpc, :schema
alias Immudb.Socket
alias Immudb.Util
@spec connection(String.t(), integer) :: GRPC.Channel.t()
defp connection(host, port) do
"#{host}:#{port}"
|> GRPC.Stub.connect(interceptors: [GRPC.Logger.Client])
end
@spec new(
host: String.t(),
port: integer(),
username: String.t(),
password: String.t(),
database: String.t()
) :: {:ok, Socket.t()} | {:error, String.t()}
def new(
host: host,
port: port,
username: username,
password: password,
database: database
) do
with {:grpc_connect, {:ok, channel}} <-
{:grpc_connect, connection(host, port)},
{:immudb_login, {:ok, token}} <-
{:immudb_login,
channel
|> login(username, password)},
socket <- %Socket{channel: channel, token: token},
{:immudb_use_database, {:ok, token}} <-
{:immudb_use_database,
socket
|> Immudb.Database.use_database(database)} do
{:ok, %Socket{channel: channel, token: token}}
else
{:grpc_connect, {:error, e}} ->
{:error, e}
{:immudb_login, {:error, e}} ->
{:error, e}
{:immudb_use_database, {:error, e}} ->
{:error, e}
end
end
@spec new(url: String.t()) :: {:ok, Socket.t()} | {:error, String.t()}
def new(url: url) do
with {:parse_uri, {:ok, uri}} <- {:parse_uri, URI.new(url)},
{:is_immudb_schema, true} <- {:is_immudb_schema, uri.scheme == "immudb"},
userinfo <- uri.userinfo |> String.split(":", trim: true),
{:is_userinfo, true} <- {:is_userinfo, userinfo |> length > 0} do
{username, password} =
userinfo
|> case do
[username] -> {username, ""}
[username, password] -> {username, password}
_ -> {"", ""}
end
new(
host: uri.host,
port: uri.port,
username: username,
password: password,
database: uri.path |> String.slice(1, String.length(uri.path) - 1)
)
else
{:parse_uri, {:error, _}} -> {:error, "Invalid connection string"}
{:is_immudb_schema, false} -> {:error, "Invalid connection string"}
{:is_userinfo, false} -> {:error, "Invalid user info"}
end
end
def new(_) do
{:error, :invalid_params}
end
@spec list_users(Socket.t()) ::
{:error, String.t() | atom()} | {:ok, [User.t()]}
def list_users(%Socket{channel: %GRPC.Channel{} = channel, token: token}) do
channel
|> Stub.list_users(Google.Protobuf.Empty.new(), metadata: token |> Util.metadata())
|> case do
{:ok, %{users: users}} ->
{:ok, users |> Immudb.Schemas.User.convert()}
{:error, %GRPC.RPCError{message: message}} ->
{:error, message}
_ ->
{:error, :unknown}
end
end
def list_users(_) do
{:error, :invalid_params}
end
@spec create_user(Socket.t(),
user: String.t(),
password: String.t(),
database: String.t(),
permission: atom()
) ::
{:error, String.t() | atom()} | {:ok, nil}
def create_user(%Socket{channel: %GRPC.Channel{} = channel, token: token},
user: user,
password: password,
database: database,
permission: permission
) do
channel
|> Stub.create_user(
Schema.CreateUserRequest.new(
user: user,
password: password,
permission: permission |> Immudb.Schemas.Permission.to_int(),
database: database
),
metadata: token |> Util.metadata()
)
|> case do
{:error, %GRPC.RPCError{message: message}} ->
{:error, message}
{:ok, %Google.Protobuf.Empty{}} ->
{:ok, nil}
_ ->
{:error, :unknown}
end
end
def create_user(_) do
{:error, :invalid_params}
end
@spec change_password(Socket.t(),
user: String.t(),
old_password: String.t(),
new_password: String.t()
) ::
{:error, String.t() | atom()} | {:ok, String.t()}
def change_password(%Socket{channel: %GRPC.Channel{} = channel, token: token},
user: user,
old_password: old_password,
new_password: new_password
) do
channel
|> Stub.change_password(
Schema.ChangePasswordRequest.new(
user: user,
oldPassword: old_password,
newPassword: new_password
),
metadata: token |> Util.metadata()
)
|> case do
{:error, %GRPC.RPCError{message: message}} ->
{:error, message}
{:ok, %Google.Protobuf.Empty{}} ->
{:ok, nil}
_ ->
{:error, :unknown}
end
end
def change_password(_, _) do
{:error, :invalid_params}
end
def update_auth_config(socket, params) do
socket.channel
|> Stub.update_auth_config(Schema.AuthConfig.new(kind: params.kind))
end
def update_mtls_confg(socket, params) do
socket.channel
|> Stub.update_mtls_config(Schema.MTLSConfig.new(enabled: params.enabled))
end
@spec login(GRPC.Channel.t(), String.t(), String.t()) ::
{:error, String.t() | atom()} | {:ok, String.t()}
def login(%GRPC.Channel{} = channel, user, password) do
channel
|> Stub.login(Schema.LoginRequest.new(user: user, password: password))
|> case do
{:error, %GRPC.RPCError{message: message}} ->
{:error, message}
{:ok, %{token: token}} ->
{:ok, token}
_ ->
{:error, :unknown}
end
end
def login(_, _, _) do
{:error, :invalid_params}
end
@spec logout(GRPC.Channel.t()) ::
{:error, String.t() | atom()} | {:ok, nil}
def logout(%Socket{channel: %GRPC.Channel{} = channel, token: token}) do
channel
|> Stub.logout(Protobuf.Empty.new(), metadata: token |> Util.metadata())
|> case do
{:error, %GRPC.RPCError{message: message}} ->
{:error, message}
{:ok, %Google.Protobuf.Empty{}} ->
{:ok, nil}
_ ->
{:error, :unknown}
end
end
def logout(_) do
{:error, :invalid_params}
end
@spec health(Socket.t()) ::
{:error, String.t() | atom()} | {:ok, nil}
def health(%Socket{channel: %GRPC.Channel{} = channel}) do
channel
|> Stub.health(Protobuf.Empty.new())
|> case do
{:ok, v} ->
{:ok, v}
{:error, %GRPC.RPCError{message: message}} ->
{:error, message}
_ ->
{:error, :unknown}
end
end
def health(_) do
{:error, :invalid_params}
end
@spec current_state(Socket.t()) ::
{:error, String.t() | atom()} | {:ok, nil}
def current_state(%Socket{channel: %GRPC.Channel{} = channel, token: token}) do
channel
|> Stub.current_state(Protobuf.Empty.new(), metadata: token |> Util.metadata())
|> case do
{:ok, v} ->
{:ok, v}
{:error, %GRPC.RPCError{message: message}} ->
{:error, message}
_ ->
{:error, :unknown}
end
end
def current_state(_) do
{:error, :invalid_params}
end
end