Skip to content

Commit 4722a60

Browse files
authored
Make scripts endpoint consistent with api by taking name instead of id (#2398)
Fixes #2396
1 parent 8a37ac9 commit 4722a60

File tree

5 files changed

+91
-10
lines changed

5 files changed

+91
-10
lines changed

lib/nerves_hub/scripts.ex

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,31 @@ defmodule NervesHub.Scripts do
4646
end
4747
end
4848

49+
def get_by_product_and_name(product, name) do
50+
case Repo.get_by(Script, name: name, product_id: product.id) do
51+
nil ->
52+
{:error, :not_found}
53+
54+
script ->
55+
{:ok, script}
56+
end
57+
end
58+
59+
def get_by_product_and_name_with_id_fallback(product, name_or_id) do
60+
# Try to find by name first
61+
case get_by_product_and_name(product, name_or_id) do
62+
{:ok, script} ->
63+
{:ok, script}
64+
65+
{:error, :not_found} ->
66+
# If not found by name, try by ID
67+
case Integer.parse(name_or_id) do
68+
{id, ""} -> get(product, id)
69+
_ -> {:error, :not_found}
70+
end
71+
end
72+
end
73+
4974
@spec create(Product.t(), User.t(), map()) :: {:ok, Script.t()} | {:error, Changeset.t()}
5075
def create(product, user, params) do
5176
Script.create_changeset(product, user, params)

lib/nerves_hub_web/controllers/api/openapi/device_controller_specs.ex

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -293,16 +293,21 @@ defmodule NervesHubWeb.API.OpenAPI.DeviceControllerSpecs do
293293

294294
additional_parameters = [
295295
%OpenApiSpex.Parameter{
296-
name: :script_id,
296+
name: :name_or_id,
297297
in: :path,
298-
description: "Support Script ID",
298+
description: "Support Script Name or ID. Name takes priority.",
299299
required: true,
300-
schema: %OpenApiSpex.Schema{type: :integer},
301-
example: "123"
300+
schema: %OpenApiSpex.Schema{
301+
oneOf: [
302+
%OpenApiSpex.Schema{type: :string},
303+
%OpenApiSpex.Schema{type: :integer}
304+
]
305+
},
306+
example: "my-script"
302307
},
303308
%OpenApiSpex.Parameter{
304309
name: :timeout,
305-
in: :path,
310+
in: :query,
306311
description: "How long to wait for a device response in milliseconds",
307312
required: false,
308313
schema: %OpenApiSpex.Schema{type: :integer},
@@ -325,7 +330,7 @@ defmodule NervesHubWeb.API.OpenAPI.DeviceControllerSpecs do
325330

326331
add_to_paths(
327332
openapi,
328-
"#{opts.path_prefix}/scripts/{script_id}",
333+
"#{opts.path_prefix}/scripts/{name_or_id}",
329334
%OpenApiSpex.PathItem{post: send_script_operation}
330335
)
331336
end

lib/nerves_hub_web/controllers/api/script_controller.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ defmodule NervesHubWeb.API.ScriptController do
2424
# This operation is defined in `NervesHubWeb.API.OpenAPI.DeviceControllerSpecs`
2525
operation(:send, false)
2626

27-
def send(%{assigns: %{device: device}} = conn, %{"id" => id} = params) do
28-
with {:ok, command} <- Scripts.get(device.product, id),
27+
def send(%{assigns: %{device: device}} = conn, %{"name_or_id" => name_or_id} = params) do
28+
with {:ok, command} <- Scripts.get_by_product_and_name_with_id_fallback(device.product, name_or_id),
2929
{:ok, timeout} <- get_timeout_param(params),
3030
{:ok, io} <- Scripts.Runner.send(device, command, timeout) do
3131
text(conn, io)

lib/nerves_hub_web/router.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ defmodule NervesHubWeb.Router do
8989
delete("/penalty", DeviceController, :penalty)
9090

9191
get("/scripts", ScriptController, :index)
92-
post("/scripts/:id", ScriptController, :send)
92+
post("/scripts/:name_or_id", ScriptController, :send)
9393
end
9494

9595
scope "/" do
@@ -154,7 +154,7 @@ defmodule NervesHubWeb.Router do
154154
delete("/penalty", DeviceController, :penalty)
155155

156156
scope "/scripts", as: :device do
157-
post("/:id", ScriptController, :send)
157+
post("/:name_or_id", ScriptController, :send)
158158
end
159159

160160
scope "/certificates" do

test/nerves_hub_web/controllers/api/script_controller_test.exs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
defmodule NervesHubWeb.API.ScriptControllerTest do
22
use NervesHubWeb.APIConnCase, async: true
3+
use Mimic
34

45
alias NervesHub.Fixtures
56

@@ -44,4 +45,54 @@ defmodule NervesHubWeb.API.ScriptControllerTest do
4445
assert script_response["id"] == script_with_tags.id
4546
end
4647
end
48+
49+
describe "send" do
50+
test "sends script to device by name", %{conn: conn, device: device, product: product, user: user} do
51+
script = Fixtures.support_script_fixture(product, user, %{name: "test-script"})
52+
53+
path = Routes.api_script_path(conn, :send, device, script.name)
54+
55+
NervesHub.Scripts.Runner
56+
|> expect(:send, fn _, _, _ -> {:ok, "hello"} end)
57+
58+
conn
59+
|> post(path)
60+
|> response(200)
61+
end
62+
63+
test "sends script to device by id", %{conn: conn, device: device, product: product, user: user} do
64+
script = Fixtures.support_script_fixture(product, user)
65+
66+
path = Routes.api_script_path(conn, :send, device, script.id)
67+
68+
NervesHub.Scripts.Runner
69+
|> expect(:send, fn _, _, _ -> {:ok, "hello"} end)
70+
71+
conn
72+
|> post(path)
73+
|> response(200)
74+
end
75+
76+
test "returns error when script not found by name", %{conn: conn, device: device} do
77+
path = Routes.api_script_path(conn, :send, device, "nonexistent-script")
78+
79+
resp =
80+
conn
81+
|> post(path)
82+
|> json_response(503)
83+
84+
assert resp == %{"errors" => %{"detail" => "not_found"}}
85+
end
86+
87+
test "returns error when script not found by id", %{conn: conn, device: device} do
88+
path = Routes.api_script_path(conn, :send, device, 99_999)
89+
90+
resp =
91+
conn
92+
|> post(path)
93+
|> json_response(503)
94+
95+
assert resp == %{"errors" => %{"detail" => "not_found"}}
96+
end
97+
end
4798
end

0 commit comments

Comments
 (0)