@adventurelabs/scout-core 1.0.119 → 1.0.121

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1,2 @@
1
1
  export declare const BUCKET_NAME_SCOUT = "scout";
2
+ export declare const SIGNED_URL_EXPIRATION_SECONDS: number;
@@ -1 +1,3 @@
1
1
  export const BUCKET_NAME_SCOUT = "scout";
2
+ // Signed URL expiration time in seconds (12 hours = 43200 seconds)
3
+ export const SIGNED_URL_EXPIRATION_SECONDS = 12 * 60 * 60;
@@ -0,0 +1,9 @@
1
+ import { IArtifactWithMediaUrl } from "../types/db";
2
+ import { IWebResponseCompatible } from "../types/requests";
3
+ import { SupabaseClient } from "@supabase/supabase-js";
4
+ export declare function server_get_artifacts_by_herd(herd_id: number, limit?: number, offset?: number, client?: SupabaseClient): Promise<IWebResponseCompatible<IArtifactWithMediaUrl[]>>;
5
+ export declare function server_get_artifacts_by_device_id(device_id: number, limit?: number, offset?: number, client?: SupabaseClient): Promise<IWebResponseCompatible<IArtifactWithMediaUrl[]>>;
6
+ export declare function server_get_total_artifacts_by_herd(herd_id: number): Promise<IWebResponseCompatible<number>>;
7
+ export declare function server_get_artifacts_by_device_ids_batch(device_ids: number[], limit_per_device?: number, client?: SupabaseClient): Promise<{
8
+ [device_id: number]: IArtifactWithMediaUrl[];
9
+ }>;
@@ -0,0 +1,121 @@
1
+ "use server";
2
+ import { newServerClient } from "../supabase/server";
3
+ import { IWebResponse, } from "../types/requests";
4
+ import { generateSignedUrlsBatch } from "./storage";
5
+ export async function server_get_artifacts_by_herd(herd_id, limit = 50, offset = 0, client) {
6
+ const supabase = client || (await newServerClient());
7
+ const { data, error } = await supabase.rpc("get_artifacts_for_herd", {
8
+ herd_id_caller: herd_id,
9
+ limit_caller: limit,
10
+ offset_caller: offset,
11
+ });
12
+ if (error) {
13
+ return IWebResponse.error(error.message).to_compatible();
14
+ }
15
+ if (!data) {
16
+ return IWebResponse.error("No artifacts found for herd").to_compatible();
17
+ }
18
+ // Generate signed URLs for artifacts
19
+ const uniqueFilePaths = Array.from(new Set(data
20
+ .map((artifact) => artifact.file_path)
21
+ .filter((path) => !!path)));
22
+ if (uniqueFilePaths.length === 0) {
23
+ return IWebResponse.success(data).to_compatible();
24
+ }
25
+ const signedUrls = await generateSignedUrlsBatch(uniqueFilePaths, undefined, client);
26
+ const urlMap = new Map();
27
+ uniqueFilePaths.forEach((path, index) => {
28
+ urlMap.set(path, signedUrls[index]);
29
+ });
30
+ const artifactsWithUrls = data.map((artifact) => ({
31
+ ...artifact,
32
+ media_url: artifact.file_path
33
+ ? urlMap.get(artifact.file_path) || null
34
+ : null,
35
+ }));
36
+ return IWebResponse.success(artifactsWithUrls).to_compatible();
37
+ }
38
+ export async function server_get_artifacts_by_device_id(device_id, limit = 50, offset = 0, client) {
39
+ const supabase = client || (await newServerClient());
40
+ const { data, error } = await supabase.rpc("get_artifacts_for_device", {
41
+ device_id_caller: device_id,
42
+ limit_caller: limit,
43
+ offset_caller: offset,
44
+ });
45
+ if (error) {
46
+ return IWebResponse.error(error.message).to_compatible();
47
+ }
48
+ if (!data) {
49
+ return IWebResponse.error("No artifacts found for device").to_compatible();
50
+ }
51
+ // Generate signed URLs for artifacts
52
+ const uniqueFilePaths = Array.from(new Set(data
53
+ .map((artifact) => artifact.file_path)
54
+ .filter((path) => !!path)));
55
+ if (uniqueFilePaths.length === 0) {
56
+ return IWebResponse.success(data).to_compatible();
57
+ }
58
+ const signedUrls = await generateSignedUrlsBatch(uniqueFilePaths, undefined, client);
59
+ const urlMap = new Map();
60
+ uniqueFilePaths.forEach((path, index) => {
61
+ urlMap.set(path, signedUrls[index]);
62
+ });
63
+ const artifactsWithUrls = data.map((artifact) => ({
64
+ ...artifact,
65
+ media_url: artifact.file_path
66
+ ? urlMap.get(artifact.file_path) || null
67
+ : null,
68
+ }));
69
+ return IWebResponse.success(artifactsWithUrls).to_compatible();
70
+ }
71
+ export async function server_get_total_artifacts_by_herd(herd_id) {
72
+ const supabase = await newServerClient();
73
+ const { data, error } = await supabase.rpc("get_total_artifacts_for_herd", {
74
+ herd_id_caller: herd_id,
75
+ });
76
+ if (error) {
77
+ return IWebResponse.error(error.message).to_compatible();
78
+ }
79
+ return IWebResponse.success(data || 0).to_compatible();
80
+ }
81
+ export async function server_get_artifacts_by_device_ids_batch(device_ids, limit_per_device = 10, client) {
82
+ const supabase = client || (await newServerClient());
83
+ if (device_ids.length === 0) {
84
+ return {};
85
+ }
86
+ const { data, error } = await supabase.rpc("get_artifacts_for_devices_batch", {
87
+ device_ids: device_ids,
88
+ limit_per_device: limit_per_device,
89
+ });
90
+ if (error || !data) {
91
+ console.warn("Error fetching artifacts batch:", error?.message);
92
+ return {};
93
+ }
94
+ // Generate signed URLs for artifacts
95
+ const uniqueFilePaths = Array.from(new Set(data
96
+ .map((artifact) => artifact.file_path)
97
+ .filter((path) => !!path)));
98
+ let artifactsWithUrls = data;
99
+ if (uniqueFilePaths.length > 0) {
100
+ const signedUrls = await generateSignedUrlsBatch(uniqueFilePaths, undefined, client);
101
+ const urlMap = new Map();
102
+ uniqueFilePaths.forEach((path, index) => {
103
+ urlMap.set(path, signedUrls[index]);
104
+ });
105
+ artifactsWithUrls = data.map((artifact) => ({
106
+ ...artifact,
107
+ media_url: artifact.file_path
108
+ ? urlMap.get(artifact.file_path) || null
109
+ : null,
110
+ }));
111
+ }
112
+ // Group artifacts by device_id
113
+ const artifactsByDevice = {};
114
+ artifactsWithUrls.forEach((artifact) => {
115
+ if (!artifactsByDevice[artifact.device_id]) {
116
+ artifactsByDevice[artifact.device_id] = [];
117
+ }
118
+ artifactsByDevice[artifact.device_id].push(artifact);
119
+ });
120
+ return artifactsByDevice;
121
+ }
@@ -21,3 +21,4 @@ export * from "./cache";
21
21
  export * from "./operators";
22
22
  export * from "./components";
23
23
  export * from "./versions_software";
24
+ export * from "./artifacts";
@@ -21,3 +21,4 @@ export * from "./cache";
21
21
  export * from "./operators";
22
22
  export * from "./components";
23
23
  export * from "./versions_software";
24
+ export * from "./artifacts";
@@ -3,18 +3,9 @@ import { Database } from "../types/supabase";
3
3
  /**
4
4
  * Generates a signed URL for a file in Supabase storage
5
5
  * @param filePath - The path to the file in storage (e.g., "events/123/image.jpg")
6
- * @param expiresIn - Number of seconds until the URL expires (default: 3600 = 1 hour)
6
+ * @param expiresIn - Number of seconds until the URL expires (default: 12 hours)
7
7
  * @param supabaseClient - Optional Supabase client (will create new one if not provided)
8
8
  * @returns Promise<string | null> - The signed URL or null if error
9
9
  */
10
10
  export declare function generateSignedUrl(filePath: string, expiresIn?: number, supabaseClient?: SupabaseClient<Database>): Promise<string | null>;
11
- export declare function generateSignedUrlsBatch(filePaths: string[], expiresIn?: number, supabaseClient?: SupabaseClient<Database>): Promise<Map<string, string | null>>;
12
- export declare function addSignedUrlsToEventsBatch(events: any[], supabaseClient?: SupabaseClient<Database>): Promise<any[]>;
13
- export declare function addSignedUrlsToEvents(events: any[], supabaseClient?: SupabaseClient<Database>): Promise<any[]>;
14
- /**
15
- * Generates a signed URL for a single event and sets it as media_url
16
- * @param event - Event object that may have file_path
17
- * @param supabaseClient - Optional Supabase client (will create new one if not provided)
18
- * @returns Promise<Object> - Event with signed URL set as media_url
19
- */
20
- export declare function addSignedUrlToEvent(event: any, supabaseClient?: SupabaseClient<Database>): Promise<any>;
11
+ export declare function generateSignedUrlsBatch(filePaths: string[], expiresIn?: number, supabaseClient?: SupabaseClient<Database>): Promise<(string | null)[]>;
@@ -1,14 +1,14 @@
1
1
  "use server";
2
2
  import { newServerClient } from "../supabase/server";
3
- import { BUCKET_NAME_SCOUT } from "../constants/db";
3
+ import { BUCKET_NAME_SCOUT, SIGNED_URL_EXPIRATION_SECONDS, } from "../constants/db";
4
4
  /**
5
5
  * Generates a signed URL for a file in Supabase storage
6
6
  * @param filePath - The path to the file in storage (e.g., "events/123/image.jpg")
7
- * @param expiresIn - Number of seconds until the URL expires (default: 3600 = 1 hour)
7
+ * @param expiresIn - Number of seconds until the URL expires (default: 12 hours)
8
8
  * @param supabaseClient - Optional Supabase client (will create new one if not provided)
9
9
  * @returns Promise<string | null> - The signed URL or null if error
10
10
  */
11
- export async function generateSignedUrl(filePath, expiresIn = 3600, supabaseClient) {
11
+ export async function generateSignedUrl(filePath, expiresIn = SIGNED_URL_EXPIRATION_SECONDS, supabaseClient) {
12
12
  try {
13
13
  const supabase = supabaseClient || (await newServerClient());
14
14
  const { data, error } = await supabase.storage
@@ -25,10 +25,9 @@ export async function generateSignedUrl(filePath, expiresIn = 3600, supabaseClie
25
25
  return null;
26
26
  }
27
27
  }
28
- export async function generateSignedUrlsBatch(filePaths, expiresIn = 3600, supabaseClient) {
28
+ export async function generateSignedUrlsBatch(filePaths, expiresIn = SIGNED_URL_EXPIRATION_SECONDS, supabaseClient) {
29
29
  try {
30
30
  const supabase = supabaseClient || (await newServerClient());
31
- const urlMap = new Map();
32
31
  const signedUrlPromises = filePaths.map(async (filePath) => {
33
32
  try {
34
33
  const { data, error } = await supabase.storage
@@ -36,63 +35,20 @@ export async function generateSignedUrlsBatch(filePaths, expiresIn = 3600, supab
36
35
  .createSignedUrl(filePath, expiresIn);
37
36
  if (error) {
38
37
  console.error(`Error generating signed URL for ${filePath}:`, error.message);
39
- return { filePath, signedUrl: null };
38
+ return null;
40
39
  }
41
- return { filePath, signedUrl: data.signedUrl };
40
+ return data.signedUrl;
42
41
  }
43
42
  catch (error) {
44
- console.error(`Error in generateSignedUrl for ${filePath}:`, error);
45
- return { filePath, signedUrl: null };
43
+ console.error(`Exception generating signed URL for ${filePath}:`, error);
44
+ return null;
46
45
  }
47
46
  });
48
47
  const results = await Promise.all(signedUrlPromises);
49
- results.forEach(({ filePath, signedUrl }) => {
50
- urlMap.set(filePath, signedUrl);
51
- });
52
- return urlMap;
48
+ return results;
53
49
  }
54
50
  catch (error) {
55
51
  console.error("Error in generateSignedUrlsBatch:", error);
56
- return new Map();
57
- }
58
- }
59
- export async function addSignedUrlsToEventsBatch(events, supabaseClient) {
60
- const filePaths = events
61
- .map((event) => event.file_path)
62
- .filter((path) => path)
63
- .filter((path, index, array) => array.indexOf(path) === index);
64
- if (filePaths.length === 0) {
65
- return events;
66
- }
67
- const urlMap = await generateSignedUrlsBatch(filePaths, 3600, supabaseClient);
68
- return events.map((event) => {
69
- if (event.file_path && urlMap.has(event.file_path)) {
70
- const signedUrl = urlMap.get(event.file_path);
71
- return {
72
- ...event,
73
- media_url: signedUrl || event.media_url,
74
- };
75
- }
76
- return event;
77
- });
78
- }
79
- export async function addSignedUrlsToEvents(events, supabaseClient) {
80
- return addSignedUrlsToEventsBatch(events, supabaseClient);
81
- }
82
- /**
83
- * Generates a signed URL for a single event and sets it as media_url
84
- * @param event - Event object that may have file_path
85
- * @param supabaseClient - Optional Supabase client (will create new one if not provided)
86
- * @returns Promise<Object> - Event with signed URL set as media_url
87
- */
88
- export async function addSignedUrlToEvent(event, supabaseClient) {
89
- // If event has a file_path, generate a signed URL and set it as media_url
90
- if (event.file_path) {
91
- const signedUrl = await generateSignedUrl(event.file_path, 3600, supabaseClient);
92
- return {
93
- ...event,
94
- media_url: signedUrl || event.media_url, // Fall back to existing media_url if signed URL fails
95
- };
52
+ return new Array(filePaths.length).fill(null);
96
53
  }
97
- return event;
98
54
  }
@@ -2,7 +2,7 @@
2
2
  // add tag to db
3
3
  import { newServerClient } from "../supabase/server";
4
4
  import { EnumWebResponse, IWebResponse, } from "../types/requests";
5
- import { addSignedUrlsToEvents, addSignedUrlToEvent } from "./storage";
5
+ import { generateSignedUrlsBatch, generateSignedUrl } from "./storage";
6
6
  // Helper functions to extract coordinates from location field
7
7
  function extractLatitude(location) {
8
8
  try {
@@ -169,8 +169,22 @@ export async function server_get_more_events_with_tags_by_herd(herd_id, offset,
169
169
  event.tags = event.tags.filter((tag) => tag !== null);
170
170
  return event;
171
171
  });
172
- // Add signed URLs to events using the same client
173
- const eventsWithSignedUrls = await addSignedUrlsToEvents(filtered_data, supabase);
172
+ // Generate signed URLs for events
173
+ const filePaths = filtered_data
174
+ .map((event) => event.file_path)
175
+ .filter((path) => !!path);
176
+ let eventsWithSignedUrls = filtered_data;
177
+ if (filePaths.length > 0) {
178
+ const signedUrls = await generateSignedUrlsBatch(filePaths, undefined, supabase);
179
+ const urlMap = new Map();
180
+ filePaths.forEach((path, index) => {
181
+ urlMap.set(path, signedUrls[index]);
182
+ });
183
+ eventsWithSignedUrls = filtered_data.map((event) => ({
184
+ ...event,
185
+ media_url: event.file_path ? urlMap.get(event.file_path) || null : null,
186
+ }));
187
+ }
174
188
  return IWebResponse.success(eventsWithSignedUrls).to_compatible();
175
189
  }
176
190
  export async function server_get_events_and_tags_for_device(device_id, limit = 3) {
@@ -188,8 +202,23 @@ export async function server_get_events_and_tags_for_device(device_id, limit = 3
188
202
  data: [],
189
203
  };
190
204
  }
191
- // Add signed URLs to events using the same client
192
- const eventsWithSignedUrls = await addSignedUrlsToEvents(data || [], supabase);
205
+ // Generate signed URLs for events
206
+ const eventData = data || [];
207
+ const filePaths = eventData
208
+ .map((event) => event.file_path)
209
+ .filter((path) => !!path);
210
+ let eventsWithSignedUrls = eventData;
211
+ if (filePaths.length > 0) {
212
+ const signedUrls = await generateSignedUrlsBatch(filePaths, undefined, supabase);
213
+ const urlMap = new Map();
214
+ filePaths.forEach((path, index) => {
215
+ urlMap.set(path, signedUrls[index]);
216
+ });
217
+ eventsWithSignedUrls = eventData.map((event) => ({
218
+ ...event,
219
+ media_url: event.file_path ? urlMap.get(event.file_path) || null : null,
220
+ }));
221
+ }
193
222
  return IWebResponse.success(eventsWithSignedUrls).to_compatible();
194
223
  }
195
224
  export async function server_get_events_and_tags_for_devices_batch(device_ids, limit = 1) {
@@ -256,9 +285,31 @@ export async function server_get_events_and_tags_for_devices_batch(device_ids, l
256
285
  });
257
286
  // Add signed URLs to all events
258
287
  const result = {};
288
+ // Get all file paths for batch URL generation
289
+ const allFilePaths = [];
290
+ for (const device_id in eventsByDevice) {
291
+ const events = eventsByDevice[device_id];
292
+ events.forEach((event) => {
293
+ if (event.file_path && !allFilePaths.includes(event.file_path)) {
294
+ allFilePaths.push(event.file_path);
295
+ }
296
+ });
297
+ }
298
+ // Generate all signed URLs at once
299
+ const signedUrls = allFilePaths.length > 0
300
+ ? await generateSignedUrlsBatch(allFilePaths, undefined, supabase)
301
+ : [];
302
+ const urlMap = new Map();
303
+ allFilePaths.forEach((path, index) => {
304
+ urlMap.set(path, signedUrls[index]);
305
+ });
306
+ // Apply signed URLs to events
259
307
  for (const device_id in eventsByDevice) {
260
308
  const events = eventsByDevice[device_id];
261
- const eventsWithSignedUrls = await addSignedUrlsToEvents(events, supabase);
309
+ const eventsWithSignedUrls = events.map((event) => ({
310
+ ...event,
311
+ media_url: event.file_path ? urlMap.get(event.file_path) || null : null,
312
+ }));
262
313
  result[parseInt(device_id)] = eventsWithSignedUrls;
263
314
  }
264
315
  return IWebResponse.success(result).to_compatible();
@@ -369,7 +420,14 @@ export async function get_event_and_tags_by_event_id(event_id) {
369
420
  earthranger_url: data[0].earthranger_url,
370
421
  file_path: data[0].file_path,
371
422
  };
372
- // Add signed URL to event using the same client
373
- const eventWithSignedUrl = await addSignedUrlToEvent(transformedData, supabase);
423
+ // Generate signed URL for event
424
+ let eventWithSignedUrl = transformedData;
425
+ if (transformedData.file_path) {
426
+ const signedUrl = await generateSignedUrl(transformedData.file_path, undefined, supabase);
427
+ eventWithSignedUrl = {
428
+ ...transformedData,
429
+ media_url: signedUrl,
430
+ };
431
+ }
374
432
  return IWebResponse.success(eventWithSignedUrl).to_compatible();
375
433
  }
package/dist/index.d.ts CHANGED
@@ -36,6 +36,8 @@ export * from "./helpers/cache";
36
36
  export * from "./helpers/heartbeats";
37
37
  export * from "./helpers/providers";
38
38
  export * from "./helpers/operators";
39
+ export * from "./helpers/versions_software";
40
+ export * from "./helpers/components";
39
41
  export * from "./hooks/useScoutRealtimeConnectivity";
40
42
  export * from "./hooks/useScoutRealtimeDevices";
41
43
  export * from "./hooks/useScoutRefresh";
package/dist/index.js CHANGED
@@ -39,6 +39,8 @@ export * from "./helpers/cache";
39
39
  export * from "./helpers/heartbeats";
40
40
  export * from "./helpers/providers";
41
41
  export * from "./helpers/operators";
42
+ export * from "./helpers/versions_software";
43
+ export * from "./helpers/components";
42
44
  // Hooks
43
45
  export * from "./hooks/useScoutRealtimeConnectivity";
44
46
  export * from "./hooks/useScoutRealtimeDevices";
@@ -1037,6 +1037,74 @@ export declare function useSupabase(): SupabaseClient<Database, "public", "publi
1037
1037
  status: string;
1038
1038
  }[];
1039
1039
  };
1040
+ get_artifacts_for_device: {
1041
+ Args: {
1042
+ device_id_caller: number;
1043
+ limit_caller?: number;
1044
+ offset_caller?: number;
1045
+ };
1046
+ Returns: {
1047
+ created_at: string;
1048
+ device_id: number;
1049
+ file_path: string;
1050
+ id: number;
1051
+ modality: string | null;
1052
+ session_id: number | null;
1053
+ timestamp_observation: string | null;
1054
+ updated_at: string | null;
1055
+ }[];
1056
+ SetofOptions: {
1057
+ from: "*";
1058
+ to: "artifacts";
1059
+ isOneToOne: false;
1060
+ isSetofReturn: true;
1061
+ };
1062
+ };
1063
+ get_artifacts_for_devices_batch: {
1064
+ Args: {
1065
+ device_ids: number[];
1066
+ limit_per_device?: number;
1067
+ };
1068
+ Returns: {
1069
+ created_at: string;
1070
+ device_id: number;
1071
+ file_path: string;
1072
+ id: number;
1073
+ modality: string | null;
1074
+ session_id: number | null;
1075
+ timestamp_observation: string | null;
1076
+ updated_at: string | null;
1077
+ }[];
1078
+ SetofOptions: {
1079
+ from: "*";
1080
+ to: "artifacts";
1081
+ isOneToOne: false;
1082
+ isSetofReturn: true;
1083
+ };
1084
+ };
1085
+ get_artifacts_for_herd: {
1086
+ Args: {
1087
+ herd_id_caller: number;
1088
+ limit_caller?: number;
1089
+ offset_caller?: number;
1090
+ };
1091
+ Returns: {
1092
+ created_at: string;
1093
+ device_id: number;
1094
+ file_path: string;
1095
+ id: number;
1096
+ modality: string | null;
1097
+ session_id: number | null;
1098
+ timestamp_observation: string | null;
1099
+ updated_at: string | null;
1100
+ }[];
1101
+ SetofOptions: {
1102
+ from: "*";
1103
+ to: "artifacts";
1104
+ isOneToOne: false;
1105
+ isSetofReturn: true;
1106
+ };
1107
+ };
1040
1108
  get_connectivity_with_coordinates: {
1041
1109
  Args: {
1042
1110
  session_id_caller: number;
@@ -1212,6 +1280,12 @@ export declare function useSupabase(): SupabaseClient<Database, "public", "publi
1212
1280
  isSetofReturn: true;
1213
1281
  };
1214
1282
  };
1283
+ get_total_artifacts_for_herd: {
1284
+ Args: {
1285
+ herd_id_caller: number;
1286
+ };
1287
+ Returns: number;
1288
+ };
1215
1289
  get_total_events_for_herd_with_session_filter: {
1216
1290
  Args: {
1217
1291
  exclude_session_events: boolean;
@@ -88,6 +88,14 @@ export declare const scoutSlice: import("@reduxjs/toolkit").Slice<ScoutState, {
88
88
  payload: any;
89
89
  type: string;
90
90
  }) => void;
91
+ replaceArtifactsForHerdModule: (state: import("immer").WritableDraft<ScoutState>, action: {
92
+ payload: any;
93
+ type: string;
94
+ }) => void;
95
+ appendArtifactsToHerdModule: (state: import("immer").WritableDraft<ScoutState>, action: {
96
+ payload: any;
97
+ type: string;
98
+ }) => void;
91
99
  appendPlansToHerdModule: (state: import("immer").WritableDraft<ScoutState>, action: {
92
100
  payload: any;
93
101
  type: string;
@@ -165,6 +173,6 @@ export declare const scoutSlice: import("@reduxjs/toolkit").Slice<ScoutState, {
165
173
  type: string;
166
174
  }) => void;
167
175
  }, "scout", "scout", import("@reduxjs/toolkit").SliceSelectors<ScoutState>>;
168
- export declare const setHerdModules: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModules">, setStatus: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setStatus">, setHerdModulesLoadingState: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesLoadingState">, setHerdModulesLoadedInMs: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesLoadedInMs">, setHerdModulesApiServerProcessingDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesApiServerProcessingDuration">, setHerdModulesApiTotalRequestDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesApiTotalRequestDuration">, setUserApiDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setUserApiDuration">, setDataProcessingDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataProcessingDuration">, setCacheLoadDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setCacheLoadDuration">, setActiveHerdId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveHerdId">, setActiveDeviceId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveDeviceId">, setDataSource: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataSource">, setDataSourceInfo: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataSourceInfo">, appendEventsToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/appendEventsToHerdModule">, replaceEventsForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/replaceEventsForHerdModule">, updateEventValuesForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateEventValuesForHerdModule">, updatePageIndexForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updatePageIndexForHerdModule">, appendPlansToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/appendPlansToHerdModule">, setUser: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setUser">, addTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addTag">, deleteTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteTag">, updateTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateTag">, addNewDeviceToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addNewDeviceToHerdModule">, updateDeviceForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateDeviceForHerdModule">, addDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addDevice">, deleteDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteDevice">, updateDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateDevice">, addPlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addPlan">, deletePlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deletePlan">, updatePlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updatePlan">, addSessionToStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addSessionToStore">, deleteSessionFromStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteSessionFromStore">, updateSessionInStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateSessionInStore">, setActiveHerdGpsTrackersConnectivity: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveHerdGpsTrackersConnectivity">;
176
+ export declare const setHerdModules: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModules">, setStatus: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setStatus">, setHerdModulesLoadingState: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesLoadingState">, setHerdModulesLoadedInMs: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesLoadedInMs">, setHerdModulesApiServerProcessingDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesApiServerProcessingDuration">, setHerdModulesApiTotalRequestDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesApiTotalRequestDuration">, setUserApiDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setUserApiDuration">, setDataProcessingDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataProcessingDuration">, setCacheLoadDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setCacheLoadDuration">, setActiveHerdId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveHerdId">, setActiveDeviceId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveDeviceId">, setDataSource: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataSource">, setDataSourceInfo: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataSourceInfo">, appendEventsToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/appendEventsToHerdModule">, replaceEventsForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/replaceEventsForHerdModule">, appendArtifactsToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/appendArtifactsToHerdModule">, replaceArtifactsForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/replaceArtifactsForHerdModule">, updateEventValuesForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateEventValuesForHerdModule">, updatePageIndexForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updatePageIndexForHerdModule">, appendPlansToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/appendPlansToHerdModule">, setUser: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setUser">, addTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addTag">, deleteTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteTag">, updateTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateTag">, addNewDeviceToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addNewDeviceToHerdModule">, updateDeviceForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateDeviceForHerdModule">, addDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addDevice">, deleteDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteDevice">, updateDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateDevice">, addPlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addPlan">, deletePlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deletePlan">, updatePlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updatePlan">, addSessionToStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addSessionToStore">, deleteSessionFromStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteSessionFromStore">, updateSessionInStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateSessionInStore">, setActiveHerdGpsTrackersConnectivity: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveHerdGpsTrackersConnectivity">;
169
177
  declare const _default: import("redux").Reducer<ScoutState>;
170
178
  export default _default;
@@ -85,6 +85,20 @@ export const scoutSlice = createSlice({
85
85
  herd_module.events = [...herd_module.events, ...events];
86
86
  }
87
87
  },
88
+ replaceArtifactsForHerdModule: (state, action) => {
89
+ const { herd_id, artifacts } = action.payload;
90
+ const herd_module = state.herd_modules.find((hm) => hm.herd.id.toString() === herd_id);
91
+ if (herd_module) {
92
+ herd_module.artifacts = artifacts;
93
+ }
94
+ },
95
+ appendArtifactsToHerdModule: (state, action) => {
96
+ const { herd_id, artifacts } = action.payload;
97
+ const herd_module = state.herd_modules.find((hm) => hm.herd.id.toString() === herd_id);
98
+ if (herd_module) {
99
+ herd_module.artifacts = [...herd_module.artifacts, ...artifacts];
100
+ }
101
+ },
88
102
  appendPlansToHerdModule: (state, action) => {
89
103
  const { herd_id, plan } = action.payload;
90
104
  const herd_module = state.herd_modules.find((hm) => hm.herd.id.toString() === herd_id);
@@ -273,5 +287,5 @@ export const scoutSlice = createSlice({
273
287
  },
274
288
  });
275
289
  // Action creators are generated for each case reducer function
276
- export const { setHerdModules, setStatus, setHerdModulesLoadingState, setHerdModulesLoadedInMs, setHerdModulesApiServerProcessingDuration, setHerdModulesApiTotalRequestDuration, setUserApiDuration, setDataProcessingDuration, setCacheLoadDuration, setActiveHerdId, setActiveDeviceId, setDataSource, setDataSourceInfo, appendEventsToHerdModule, replaceEventsForHerdModule, updateEventValuesForHerdModule, updatePageIndexForHerdModule, appendPlansToHerdModule, setUser, addTag, deleteTag, updateTag, addNewDeviceToHerdModule, updateDeviceForHerdModule, addDevice, deleteDevice, updateDevice, addPlan, deletePlan, updatePlan, addSessionToStore, deleteSessionFromStore, updateSessionInStore, setActiveHerdGpsTrackersConnectivity, } = scoutSlice.actions;
290
+ export const { setHerdModules, setStatus, setHerdModulesLoadingState, setHerdModulesLoadedInMs, setHerdModulesApiServerProcessingDuration, setHerdModulesApiTotalRequestDuration, setUserApiDuration, setDataProcessingDuration, setCacheLoadDuration, setActiveHerdId, setActiveDeviceId, setDataSource, setDataSourceInfo, appendEventsToHerdModule, replaceEventsForHerdModule, appendArtifactsToHerdModule, replaceArtifactsForHerdModule, updateEventValuesForHerdModule, updatePageIndexForHerdModule, appendPlansToHerdModule, setUser, addTag, deleteTag, updateTag, addNewDeviceToHerdModule, updateDeviceForHerdModule, addDevice, deleteDevice, updateDevice, addPlan, deletePlan, updatePlan, addSessionToStore, deleteSessionFromStore, updateSessionInStore, setActiveHerdGpsTrackersConnectivity, } = scoutSlice.actions;
277
291
  export default scoutSlice.reducer;
@@ -26,8 +26,13 @@ export type IOperator = Database["public"]["Tables"]["operators"]["Row"];
26
26
  export type IProvider = Database["public"]["Tables"]["providers"]["Row"];
27
27
  export type IComponent = Database["public"]["Tables"]["components"]["Row"];
28
28
  export type IVersionsSoftware = Database["public"]["Tables"]["versions_software"]["Row"];
29
+ export type IArtifact = Database["public"]["Tables"]["artifacts"]["Row"];
30
+ export type IArtifactWithMediaUrl = IArtifact & {
31
+ media_url?: string | null;
32
+ };
29
33
  export type ComponentInsert = Database["public"]["Tables"]["components"]["Insert"];
30
34
  export type VersionsSoftwareInsert = Database["public"]["Tables"]["versions_software"]["Insert"];
35
+ export type ArtifactInsert = Database["public"]["Tables"]["artifacts"]["Insert"];
31
36
  export type IEventWithTags = Database["public"]["CompositeTypes"]["event_with_tags"] & {
32
37
  earthranger_url: string | null;
33
38
  file_path: string | null;
@@ -1,5 +1,5 @@
1
1
  import { SupabaseClient } from "@supabase/supabase-js";
2
- import { IDevice, IEventWithTags, IHerd, IPlan, ILayer, IProvider, IUserAndRole, IZoneWithActions, ISessionWithCoordinates } from "../types/db";
2
+ import { IDevice, IEventWithTags, IHerd, IPlan, ILayer, IProvider, IUserAndRole, IZoneWithActions, ISessionWithCoordinates, IArtifactWithMediaUrl } from "../types/db";
3
3
  import { EnumWebResponse } from "./requests";
4
4
  export declare enum EnumHerdModulesLoadingState {
5
5
  NOT_LOADING = "NOT_LOADING",
@@ -13,16 +13,18 @@ export declare class HerdModule {
13
13
  events: IEventWithTags[];
14
14
  zones: IZoneWithActions[];
15
15
  sessions: ISessionWithCoordinates[];
16
+ artifacts: IArtifactWithMediaUrl[];
16
17
  timestamp_last_refreshed: number;
17
18
  user_roles: IUserAndRole[] | null;
18
19
  events_page_index: number;
19
20
  total_events: number;
20
21
  total_events_with_filters: number;
22
+ total_artifacts: number;
21
23
  labels: string[];
22
24
  plans: IPlan[];
23
25
  layers: ILayer[];
24
26
  providers: IProvider[];
25
- constructor(herd: IHerd, devices: IDevice[], events: IEventWithTags[], timestamp_last_refreshed: number, user_roles?: IUserAndRole[] | null, events_page_index?: number, total_events?: number, total_events_with_filters?: number, labels?: string[], plans?: IPlan[], zones?: IZoneWithActions[], sessions?: ISessionWithCoordinates[], layers?: ILayer[], providers?: IProvider[]);
27
+ constructor(herd: IHerd, devices: IDevice[], events: IEventWithTags[], timestamp_last_refreshed: number, user_roles?: IUserAndRole[] | null, events_page_index?: number, total_events?: number, total_events_with_filters?: number, labels?: string[], plans?: IPlan[], zones?: IZoneWithActions[], sessions?: ISessionWithCoordinates[], layers?: ILayer[], providers?: IProvider[], artifacts?: IArtifactWithMediaUrl[], total_artifacts?: number);
26
28
  to_serializable(): IHerdModule;
27
29
  static from_herd(herd: IHerd, client: SupabaseClient): Promise<HerdModule>;
28
30
  }
@@ -41,6 +43,8 @@ export interface IHerdModule {
41
43
  sessions: ISessionWithCoordinates[];
42
44
  layers: ILayer[];
43
45
  providers: IProvider[];
46
+ artifacts: IArtifactWithMediaUrl[];
47
+ total_artifacts: number;
44
48
  }
45
49
  export interface IHerdModulesResponse {
46
50
  data: IHerdModule[];
@@ -10,6 +10,7 @@ import { EnumWebResponse } from "./requests";
10
10
  import { server_get_more_zones_and_actions_for_herd } from "../helpers/zones";
11
11
  import { server_list_api_keys_batch } from "../api_keys/actions";
12
12
  import { server_get_sessions_by_herd_id } from "../helpers/sessions";
13
+ import { server_get_artifacts_by_herd, server_get_total_artifacts_by_herd, } from "../helpers/artifacts";
13
14
  export var EnumHerdModulesLoadingState;
14
15
  (function (EnumHerdModulesLoadingState) {
15
16
  EnumHerdModulesLoadingState["NOT_LOADING"] = "NOT_LOADING";
@@ -18,11 +19,12 @@ export var EnumHerdModulesLoadingState;
18
19
  EnumHerdModulesLoadingState["UNSUCCESSFULLY_LOADED"] = "UNSUCCESSFULLY_LOADED";
19
20
  })(EnumHerdModulesLoadingState || (EnumHerdModulesLoadingState = {}));
20
21
  export class HerdModule {
21
- constructor(herd, devices, events, timestamp_last_refreshed, user_roles = null, events_page_index = 0, total_events = 0, total_events_with_filters = 0, labels = [], plans = [], zones = [], sessions = [], layers = [], providers = []) {
22
+ constructor(herd, devices, events, timestamp_last_refreshed, user_roles = null, events_page_index = 0, total_events = 0, total_events_with_filters = 0, labels = [], plans = [], zones = [], sessions = [], layers = [], providers = [], artifacts = [], total_artifacts = 0) {
22
23
  this.user_roles = null;
23
24
  this.events_page_index = 0;
24
25
  this.total_events = 0;
25
26
  this.total_events_with_filters = 0;
27
+ this.total_artifacts = 0;
26
28
  this.labels = [];
27
29
  this.plans = [];
28
30
  this.layers = [];
@@ -41,6 +43,8 @@ export class HerdModule {
41
43
  this.sessions = sessions;
42
44
  this.layers = layers;
43
45
  this.providers = providers;
46
+ this.artifacts = artifacts;
47
+ this.total_artifacts = total_artifacts;
44
48
  }
45
49
  to_serializable() {
46
50
  return {
@@ -58,6 +62,8 @@ export class HerdModule {
58
62
  sessions: this.sessions,
59
63
  layers: this.layers,
60
64
  providers: this.providers,
65
+ artifacts: this.artifacts,
66
+ total_artifacts: this.total_artifacts,
61
67
  };
62
68
  }
63
69
  static async from_herd(herd, client) {
@@ -97,6 +103,14 @@ export class HerdModule {
97
103
  console.warn(`[HerdModule] Failed to get providers:`, error);
98
104
  return { status: EnumWebResponse.ERROR, data: null };
99
105
  }),
106
+ server_get_artifacts_by_herd(herd.id, 50, 0).catch((error) => {
107
+ console.warn(`[HerdModule] Failed to get artifacts:`, error);
108
+ return { status: EnumWebResponse.ERROR, data: null };
109
+ }),
110
+ server_get_total_artifacts_by_herd(herd.id).catch((error) => {
111
+ console.warn(`[HerdModule] Failed to get total artifacts count:`, error);
112
+ return { status: EnumWebResponse.ERROR, data: null };
113
+ }),
100
114
  ]);
101
115
  // Load devices
102
116
  const devicesPromise = get_devices_by_herd(herd.id, client);
@@ -129,7 +143,7 @@ export class HerdModule {
129
143
  }
130
144
  }
131
145
  // Extract herd-level data with safe fallbacks
132
- const [res_zones, res_user_roles, total_event_count, res_plans, res_sessions, res_layers, res_providers,] = herdLevelResults;
146
+ const [res_zones, res_user_roles, total_event_count, res_plans, res_sessions, res_layers, res_providers, res_artifacts, total_artifact_count,] = herdLevelResults;
133
147
  const zones = res_zones.status === "fulfilled" && res_zones.value?.data
134
148
  ? res_zones.value.data
135
149
  : [];
@@ -152,19 +166,26 @@ export class HerdModule {
152
166
  const providers = res_providers.status === "fulfilled" && res_providers.value?.data
153
167
  ? res_providers.value.data
154
168
  : [];
169
+ const artifacts = res_artifacts.status === "fulfilled" && res_artifacts.value?.data
170
+ ? res_artifacts.value.data
171
+ : [];
172
+ const total_artifacts = total_artifact_count.status === "fulfilled" &&
173
+ total_artifact_count.value?.data
174
+ ? total_artifact_count.value.data
175
+ : 0;
155
176
  // TODO: store in DB and retrieve on load?
156
177
  const newLabels = LABELS;
157
178
  const endTime = Date.now();
158
179
  const loadTime = endTime - startTime;
159
180
  console.log(`[HerdModule] Loaded herd ${herd.slug} in ${loadTime}ms (${new_devices.length} devices)`);
160
- return new HerdModule(herd, new_devices, [], Date.now(), user_roles, 0, total_events, total_events, newLabels, plans, zones, sessions, layers, providers);
181
+ return new HerdModule(herd, new_devices, [], Date.now(), user_roles, 0, total_events, total_events, newLabels, plans, zones, sessions, layers, providers, artifacts, total_artifacts);
161
182
  }
162
183
  catch (error) {
163
184
  const endTime = Date.now();
164
185
  const loadTime = endTime - startTime;
165
186
  console.error(`[HerdModule] Critical error in HerdModule.from_herd (${loadTime}ms):`, error);
166
187
  // Return a minimal but valid HerdModule instance to prevent complete failure
167
- return new HerdModule(herd, [], [], Date.now(), null, 0, 0, 0, [], [], [], [], [], []);
188
+ return new HerdModule(herd, [], [], Date.now(), null, 0, 0, 0, [], [], [], [], [], [], [], 0);
168
189
  }
169
190
  }
170
191
  }
@@ -1091,6 +1091,74 @@ export type Database = {
1091
1091
  status: string;
1092
1092
  }[];
1093
1093
  };
1094
+ get_artifacts_for_device: {
1095
+ Args: {
1096
+ device_id_caller: number;
1097
+ limit_caller?: number;
1098
+ offset_caller?: number;
1099
+ };
1100
+ Returns: {
1101
+ created_at: string;
1102
+ device_id: number;
1103
+ file_path: string;
1104
+ id: number;
1105
+ modality: string | null;
1106
+ session_id: number | null;
1107
+ timestamp_observation: string | null;
1108
+ updated_at: string | null;
1109
+ }[];
1110
+ SetofOptions: {
1111
+ from: "*";
1112
+ to: "artifacts";
1113
+ isOneToOne: false;
1114
+ isSetofReturn: true;
1115
+ };
1116
+ };
1117
+ get_artifacts_for_devices_batch: {
1118
+ Args: {
1119
+ device_ids: number[];
1120
+ limit_per_device?: number;
1121
+ };
1122
+ Returns: {
1123
+ created_at: string;
1124
+ device_id: number;
1125
+ file_path: string;
1126
+ id: number;
1127
+ modality: string | null;
1128
+ session_id: number | null;
1129
+ timestamp_observation: string | null;
1130
+ updated_at: string | null;
1131
+ }[];
1132
+ SetofOptions: {
1133
+ from: "*";
1134
+ to: "artifacts";
1135
+ isOneToOne: false;
1136
+ isSetofReturn: true;
1137
+ };
1138
+ };
1139
+ get_artifacts_for_herd: {
1140
+ Args: {
1141
+ herd_id_caller: number;
1142
+ limit_caller?: number;
1143
+ offset_caller?: number;
1144
+ };
1145
+ Returns: {
1146
+ created_at: string;
1147
+ device_id: number;
1148
+ file_path: string;
1149
+ id: number;
1150
+ modality: string | null;
1151
+ session_id: number | null;
1152
+ timestamp_observation: string | null;
1153
+ updated_at: string | null;
1154
+ }[];
1155
+ SetofOptions: {
1156
+ from: "*";
1157
+ to: "artifacts";
1158
+ isOneToOne: false;
1159
+ isSetofReturn: true;
1160
+ };
1161
+ };
1094
1162
  get_connectivity_with_coordinates: {
1095
1163
  Args: {
1096
1164
  session_id_caller: number;
@@ -1266,6 +1334,12 @@ export type Database = {
1266
1334
  isSetofReturn: true;
1267
1335
  };
1268
1336
  };
1337
+ get_total_artifacts_for_herd: {
1338
+ Args: {
1339
+ herd_id_caller: number;
1340
+ };
1341
+ Returns: number;
1342
+ };
1269
1343
  get_total_events_for_herd_with_session_filter: {
1270
1344
  Args: {
1271
1345
  exclude_session_events: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adventurelabs/scout-core",
3
- "version": "1.0.119",
3
+ "version": "1.0.121",
4
4
  "description": "Core utilities and helpers for Adventure Labs Scout applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",