Skip to content

Commit 7e59bd7

Browse files
committed
feat: add unified file management with upload and download requests
- expose device file upload requests in backend and add a calculated presigned GET URL - introduce Device-to-Server upload request flow in UI - add upload request history with download option - merge file transfer UX into a single File Management tab Signed-off-by: Omar <omar.brbutovic@secomind.com>
1 parent 89f43e4 commit 7e59bd7

12 files changed

Lines changed: 1701 additions & 61 deletions

File tree

backend/lib/edgehog/devices/device/device.ex

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ defmodule Edgehog.Devices.Device do
6262
paginate_relationship_with application_deployments: :relay,
6363
ota_operations: :relay,
6464
tags: :relay,
65-
file_download_requests: :relay
65+
file_download_requests: :relay,
66+
file_upload_requests: :relay
6667

6768
subscriptions do
6869
pubsub EdgehogWeb.Endpoint
@@ -508,6 +509,12 @@ defmodule Edgehog.Devices.Device do
508509
writable? false
509510
end
510511

512+
has_many :file_upload_requests, Edgehog.Files.FileUploadRequest do
513+
public? true
514+
description "The existing file upload requests for this device"
515+
writable? false
516+
end
517+
511518
has_many :application_deployments, Deployment do
512519
public? true
513520
end
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#
2+
# This file is part of Edgehog.
3+
#
4+
# Copyright 2026 SECO Mind Srl
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
# SPDX-License-Identifier: Apache-2.0
19+
#
20+
21+
defmodule Edgehog.Files.FileUploadRequest.Calculations.GetPresignedUrl do
22+
@moduledoc false
23+
use Ash.Resource.Calculation
24+
25+
alias Ash.Resource.Calculation
26+
27+
@files_storage_module Application.compile_env(
28+
:edgehog,
29+
:files_storage_module,
30+
Edgehog.Storage
31+
)
32+
33+
@impl Calculation
34+
def load(_query, _opts, _context) do
35+
[:id]
36+
end
37+
38+
@impl Calculation
39+
def calculate(records, _opts, context) do
40+
tenant_id = extract_tenant_id(context)
41+
42+
Enum.map(records, fn file_upload_request ->
43+
file_path =
44+
"uploads/tenants/#{tenant_id}/file_upload_requests/#{file_upload_request.id}"
45+
46+
case @files_storage_module.read_presigned_url(file_path) do
47+
{:ok, %{get_url: get_url}} -> get_url
48+
{:error, _reason} -> nil
49+
end
50+
end)
51+
end
52+
53+
# Ash context can carry tenant as a struct in request flows or as a raw id in
54+
# background executor flows, so we support both shapes to avoid crashes.
55+
defp extract_tenant_id(%{tenant: %{tenant_id: tenant_id}}), do: tenant_id
56+
defp extract_tenant_id(%{tenant: tenant_id}), do: tenant_id
57+
defp extract_tenant_id(_), do: nil
58+
end

backend/lib/edgehog/files/file_upload_request/file_upload_request.ex

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,42 @@ defmodule Edgehog.Files.FileUploadRequest do
2222
@moduledoc false
2323
use Edgehog.MultitenantResource,
2424
domain: Edgehog.Files,
25-
extensions: [AshGraphql.Resource]
25+
extensions: [AshGraphql.Resource],
26+
notifiers: [Ash.Notifier.PubSub]
2627

28+
alias Edgehog.Files.FileUploadRequest.Calculations
2729
alias Edgehog.Files.FileUploadRequest.Changes
2830
alias Edgehog.Files.FileUploadRequest.FileSource
2931
alias Edgehog.Files.FileUploadRequest.ManualActions
3032
alias Edgehog.Files.FileUploadRequest.Status
3133

3234
graphql do
3335
type :file_upload_request
36+
37+
subscriptions do
38+
pubsub EdgehogWeb.Endpoint
39+
40+
subscribe :file_upload_requests do
41+
action_types [:create, :update]
42+
end
43+
44+
subscribe :file_upload_requests_by_device do
45+
action_types [:create, :update]
46+
read_action :read_by_device
47+
relay_id_translations device_id: :device
48+
end
49+
end
3450
end
3551

3652
actions do
3753
defaults [:read, :destroy]
3854

55+
read :read_by_device do
56+
argument :device_id, :id, allow_nil?: false
57+
58+
get_by :device_id
59+
end
60+
3961
create :send_request do
4062
accept [:source, :source_type, :compression, :progress_tracked, :http_headers]
4163

@@ -160,6 +182,16 @@ defmodule Edgehog.Files.FileUploadRequest do
160182
end
161183
end
162184

185+
calculations do
186+
calculate :get_presigned_url, :string do
187+
public? true
188+
189+
description "Get a presigned URL for downloading the uploaded file. The URL is valid for a limited time."
190+
191+
calculation Calculations.GetPresignedUrl
192+
end
193+
end
194+
163195
postgres do
164196
table "file_upload_requests"
165197
repo Edgehog.Repo

0 commit comments

Comments
 (0)