Skip to content

Commit

Permalink
feature: expose API by JSON RPC
Browse files Browse the repository at this point in the history
  • Loading branch information
vanppo committed Oct 11, 2024
1 parent c677af4 commit 96507a2
Show file tree
Hide file tree
Showing 12 changed files with 310 additions and 3 deletions.
5 changes: 3 additions & 2 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[
import_deps: [:ecto, :pluggable, :polymorphic_embed, :typed_struct],
inputs: ["{.credo,.formatter,mix}.exs", "{config,lib,test}/**/*.{ex,exs}"]
import_deps: [:ecto, :plug, :pluggable, :polymorphic_embed, :typed_struct],
inputs: ["{.credo,.formatter,mix}.exs", "{config,lib,test}/**/*.{ex,exs}"],
locals_without_parens: [rpc: 3]
]
2 changes: 2 additions & 0 deletions config/dev.exs
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
import Config

config :resource_kit, ResourceKit.Endpoint, server: true
2 changes: 1 addition & 1 deletion lib/resource_kit/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ defmodule ResourceKit.Application do
if Mix.env() === :test do
defp children, do: [ResourceKit.Repo]
else
defp children, do: []
defp children, do: [ResourceKit.Endpoint, ResourceKit.Repo]
end
end
72 changes: 72 additions & 0 deletions lib/resource_kit/controller/actions.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
defmodule ResourceKit.Controller.Actions do
@moduledoc false

alias ResourceKit.Schema.Ref
alias ResourceKit.Schema.Request

# TODO: remove this when deref is implemented
@dialyzer {:no_match, fetch_action: 1}

for type <- [:insert, :list] do
@spec unquote(type)(request :: map(), ctx :: PhxJsonRpc.Router.Context.t()) :: map()
def unquote(type)(request, _ctx) do
with {:ok, request} <- cast_request(request),
{:ok, action} <- fetch_action(request),
{:ok, result} <- run(request, unquote(type), action) do
result
end
end
end

defp cast_request(request) do
request
|> Request.changeset()
|> Ecto.Changeset.apply_action(:insert)
|> case do
{:ok, request} -> {:ok, request}
{:error, %Ecto.Changeset{errors: [reason | _rest]}} -> transform_error(reason)
end
end

defp transform_error({:type = field, {"is invalid", options}}) do
raise PhxJsonRpc.Error.InvalidParams,
message: "#{field} is invalid",
data: Keyword.fetch!(options, :enum)
end

defp transform_error({field, {"is invalid", _options}}) do
raise PhxJsonRpc.Error.InvalidParams, message: "#{field} is invalid"
end

defp transform_error({field, {"can't be blank", validation: :required}}) do
raise PhxJsonRpc.Error.InvalidParams, message: "#{field} is required"
end

defp fetch_action(%Request{uri: uri}) do
case ResourceKit.Utils.deref(%Ref{uri: uri}) do
{:ok, action} ->
{:ok, action}

{:error, {message, options}} ->
raise PhxJsonRpc.Error.InvalidParams,
message: "can't fetch action due to: '#{message}'",
data: options
end
end

defp run(%Request{params: params}, type, action) do
case apply(ResourceKit, type, [action, params]) do
{:ok, result} ->
{:ok, result}

{:error, {message, options}} ->
raise PhxJsonRpc.Error.InternalError,
message: "can't execute due to: #{message}",
data: options

{:error, %Ecto.Changeset{} = changeset} ->
raise PhxJsonRpc.Error.InternalError,
message: "can't execute due to: #{inspect(changeset)}"
end
end
end
24 changes: 24 additions & 0 deletions lib/resource_kit/endpoint.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule ResourceKit.Endpoint do
@moduledoc false

use Supervisor

@spec start_link(args :: keyword()) :: Supervisor.on_start()
def start_link(args) do
Supervisor.start_link(__MODULE__, configuration(args), name: __MODULE__)
end

@impl Supervisor
def init(args) do
{server, options} = Keyword.pop(args, :server, false)
children = if server, do: [{Bandit, options}], else: []
Supervisor.init(children, strategy: :one_for_one)
end

defp configuration(args) do
:resource_kit
|> Application.get_env(__MODULE__, [])
|> Keyword.merge(args)
|> Keyword.put(:plug, ResourceKit.Router)
end
end
24 changes: 24 additions & 0 deletions lib/resource_kit/router.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule ResourceKit.Router do
@moduledoc false

use Plug.Router

plug Plug.Logger
plug Plug.Parsers, parsers: [{:json, json_decoder: Jason}]
plug :match
plug :dispatch

post "/rpc/actions", do: handle(conn, ResourceKit.Service.Actions)

defp handle(%Plug.Conn{} = conn, service) do
import PhxJsonRpcWeb.Views.Helpers

conn
|> put_resp_header("content-type", "application/json; charset=utf-8")
|> send_json(conn.params |> service.handle() |> render_json())
end

defp send_json(%Plug.Conn{} = conn, data) do
send_resp(conn, 200, Jason.encode_to_iodata!(data))
end
end
22 changes: 22 additions & 0 deletions lib/resource_kit/schema/request.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
defmodule ResourceKit.Schema.Request do
@moduledoc false

use ResourceKit.Schema

embedded_schema do
field :uri, JetExt.Ecto.URI
field :params, :map
end

@type t() :: %__MODULE__{
uri: URI.t(),
params: map()
}

@spec changeset(schema :: %__MODULE__{}, params :: map()) :: Ecto.Changeset.t(t())
def changeset(schema \\ %__MODULE__{}, params) do
schema
|> Ecto.Changeset.cast(params, [:uri, :params])
|> Ecto.Changeset.validate_required([:uri, :params])
end
end
14 changes: 14 additions & 0 deletions lib/resource_kit/service/actions.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
defmodule ResourceKit.Service.Actions do
@moduledoc false

@dialyzer :no_behaviours

use PhxJsonRpc.Router,
max_batch_size: 10,
otp_app: :resource_kit,
schema: "priv/openrpc/services/actions.json",
version: "2.0"

rpc "insert", ResourceKit.Controller.Actions, :insert
rpc "list", ResourceKit.Controller.Actions, :list
end
3 changes: 3 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@ defmodule ResourceKit.MixProject do

defp deps do
[
{:bandit, "~> 1.5"},
{:credo, "~> 1.7", only: [:dev], runtime: false},
{:dialyxir, "~> 1.0", only: [:dev], runtime: false},
{:ecto_sql, "~> 3.12"},
{:jason, "~> 1.4"},
{:jet_credo, github: "Byzanteam/jet_credo", only: [:dev], runtime: false},
{:jet_ext, "~> 0.3.0"},
{:mimic, "~> 1.10", only: [:test], runtime: false},
{:pegasus, "~> 0.2.5"},
{:phx_json_rpc, "~> 0.6.0"},
{:pluggable, "~> 1.1"},
{:polymorphic_embed, "~> 5.0"},
{:postgrex, ">= 0.0.0"},
Expand Down
9 changes: 9 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
%{
"attrs": {:hex, :attrs, "0.6.0", "25d738b47829f964a786ef73897d2550b66f3e7d1d7c49a83bc8fd81c71bed93", [:mix], [], "hexpm", "9c30ac15255c2ba8399263db55ba32c2f4e5ec267b654ce23df99168b405c82e"},
"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"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"},
"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
Expand All @@ -8,18 +9,26 @@
"ecto": {:hex, :ecto, "3.12.4", "267c94d9f2969e6acc4dd5e3e3af5b05cdae89a4d549925f3008b2b7eb0b93c3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef04e4101688a67d061e1b10d7bc1fbf00d1d13c17eef08b71d070ff9188f747"},
"ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"},
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
"ex_json_schema": {:hex, :ex_json_schema, "0.10.2", "7c4b8c1481fdeb1741e2ce66223976edfb9bccebc8014f6aec35d4efe964fb71", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "37f43be60f8407659d4d0155a7e45e7f406dab1f827051d3d35858a709baf6a6"},
"file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
"ham": {:hex, :ham, "0.3.0", "7cd031b4a55fba219c11553e7b13ba73bd86eab4034518445eff1e038cb9a44d", [:mix], [], "hexpm", "7d6c6b73d7a6a83233876cc1b06a4d9b5de05562b228effda4532f9a49852bf6"},
"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"},
"jet_credo": {:git, "https://github.com/Byzanteam/jet_credo.git", "7e5855de2e8b41abfb0a1f5870bbc768a325f4e8", []},
"jet_ext": {:hex, :jet_ext, "0.3.0", "c68f3d46ef0e0824bbd6269e1ce4304e8330571c2a0ac3dbc03cd832161c7a69", [:mix], [{:absinthe, "~> 1.7", [hex: :absinthe, repo: "hexpm", optional: true]}, {:absinthe_relay, "~> 1.5", [hex: :absinthe_relay, repo: "hexpm", optional: true]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:typed_struct, "~> 0.3.0", [hex: :typed_struct, repo: "hexpm", optional: false]}, {:urn, "~> 1.0", [hex: :urn, repo: "hexpm", optional: true]}], "hexpm", "fd1830e4e78bebc3359017658b809c7afe9ff0d0a892a0e532d0dfa69b91ab1c"},
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mimic": {:hex, :mimic, "1.10.1", "c1e3b2044483ffa54d9e61e3be439528f47022548f6d8db1f22ca7db5490e4fa", [:mix], [{:ham, "~> 0.2", [hex: :ham, repo: "hexpm", optional: false]}], "hexpm", "b31ac70e0d6f5877af03004f02632b4fbc6abe71ed95a47d87b68d3dfffb83b5"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"pegasus": {:hex, :pegasus, "0.2.5", "38123461fe41add54f715ce41f89137a31cd217d353005b057f88b9b67c39b6f", [:mix], [{:nimble_parsec, "~> 1.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ee80708608807f4447f1da1e6e0ebd9604f5bda4fbe2d4bdd9aa6dd67afde020"},
"phx_json_rpc": {:hex, :phx_json_rpc, "0.6.0", "9e38a87c653fcd494bf84273c4dd05f75430f2197967e272f5ff69b3734e78c9", [:mix], [{:ex_json_schema, "~> 0.10.1", [hex: :ex_json_schema, repo: "hexpm", optional: false]}], "hexpm", "0e48ad8bbdce859ccd555f9273900a0bfed3f44ba78b3a44b4d3a762d42f7181"},
"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"},
"pluggable": {:hex, :pluggable, "1.1.0", "7eba3bc70c0caf4d9056c63c882df8862f7534f0145da7ab3a47ca73e4adb1e4", [:mix], [], "hexpm", "d12eb00ea47b21e92cd2700d6fbe3737f04b64e71b63aad1c0accde87c751637"},
"polymorphic_embed": {:hex, :polymorphic_embed, "5.0.0", "8edf0f8262e26d2bd884f972ab34e2ae970019ad8a4a2df8fc319117f5238d40", [:mix], [{:attrs, "~> 0.6", [hex: :attrs, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_html_helpers, "~> 1.0", [hex: :phoenix_html_helpers, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.20", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}], "hexpm", "462976e51f74858cd5cc886fd23bf74616805c11f3abe7df352dd242de1b50e6"},
"postgrex": {:hex, :postgrex, "0.19.1", "73b498508b69aded53907fe48a1fee811be34cc720e69ef4ccd568c8715495ea", [:mix], [{: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", "8bac7885a18f381e091ec6caf41bda7bb8c77912bb0e9285212829afe5d8a8f8"},
"snapshy": {:hex, :snapshy, "0.4.0", "649b7782f2e24cca8c3bf1056b7bf9c95869adbf487d44aad869c872fc41fc51", [:mix], [], "hexpm", "6928229a7fbcd7c5aa7c09ef0072b1b36eba51dfd89cf51512956a61f4c307cd"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"},
"typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
}
50 changes: 50 additions & 0 deletions priv/openrpc/services/actions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"openrpc": "1.3.2",
"info": {
"title": "Actions",
"version": "0.1.0"
},
"methods": [
{
"name": "insert",
"params": [
{
"$ref": "#/components/params/uri"
},
{
"$ref": "#/components/params/params"
}
]
},
{
"name": "list",
"params": [
{
"$ref": "#/components/params/uri"
},
{
"$ref": "#/components/params/params"
}
]
}
],
"components": {
"params": {
"uri": {
"name": "uri",
"required": true,
"schema": {
"type": "string",
"format": "uri"
}
},
"params": {
"name": "params",
"required": true,
"schema": {
"type": "object"
}
}
}
}
}
86 changes: 86 additions & 0 deletions test/resource_kit/controller/actions_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
defmodule ResourceKit.Controller.ActionsTest do
use ResourceKit.Case.Database, async: true
use ResourceKit.Case.Pipeline, async: true

@uri "volume://action:deployment@/actions/movies/insert.json"
@movie_name "movies"
@movie_columns [
{:add, :id, :uuid, primary_key: true},
{:add, :title, :text, null: false},
{:add, :likes, :numeric, null: false},
{:add, :released, :boolean, null: false},
{:add, :release_date, :date, null: false},
{:add, :created_at, :timestamp, null: false},
{:add, :tags, {:array, :text}, null: false}
]

describe "cast request" do
test "invalid uri" do
assert_raise PhxJsonRpc.Error.InvalidParams, ~r|uri is required|, fn ->
execute(:insert, %{params: %{}})
end

assert_raise PhxJsonRpc.Error.InvalidParams, ~r|uri is invalid|, fn ->
execute(:insert, %{uri: "://invalid", params: %{}})
end
end

test "invalid params" do
assert_raise PhxJsonRpc.Error.InvalidParams, ~r|params is required|, fn ->
execute(:insert, %{uri: @uri})
end

assert_raise PhxJsonRpc.Error.InvalidParams, ~r|params is invalid|, fn ->
execute(:insert, %{uri: @uri, params: []})
end
end
end

describe "fetch action" do
test "fails" do
expect(ResourceKit.Utils, :deref, fn _ref -> {:error, {"does not exist", []}} end)

assert_raise PhxJsonRpc.Error.InvalidParams, ~r|does not exist|, fn ->
execute(:insert, %{
uri: "volume://action:deployment@/actions/resources/operate.json",
params: %{}
})
end
end
end

describe "run" do
setup :setup_tables
setup :deref_json

@tag [tables: [{@movie_name, @movie_columns}]]
test "message" do
uri = "volume://action:deployment@/actions/insert_movie.json"

params = %{
"title" => "Spy x Family Code: White",
"likes" => 2878,
"released" => true,
"release_date" => "2024-04-30",
"created_at" => "2023-12-22T14:23:07Z",
"tags" => ["Animation", "Comedy"]
}

assert %{
"title" => "Spy x Family Code: White",
"likes" => %Decimal{coef: 2878},
"released" => true,
"release_date" => ~D[2024-04-30],
"created_at" => ~U[2023-12-22 14:23:07.000000Z],
"tags" => ["Animation", "Comedy"]
} = execute(:insert, %{uri: uri, params: params})
end
end

defp execute(type, request) do
alias PhxJsonRpc.Router.Context
alias ResourceKit.Controller.Actions

apply(Actions, type, [request, Context.build(Actions)])
end
end

0 comments on commit 96507a2

Please sign in to comment.