@adventurelabs/scout-core 1.0.8 → 1.0.9

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.
@@ -0,0 +1 @@
1
+ export declare const BUCKET_NAME_SCOUT = "scout";
@@ -0,0 +1 @@
1
+ export const BUCKET_NAME_SCOUT = "scout";
@@ -1,6 +1,7 @@
1
1
  "use server";
2
2
  import { newServerClient } from "../supabase/server";
3
3
  import { EnumWebResponse, IWebResponse, } from "../types/requests";
4
+ import { addSignedUrlsToEvents } from "./storage";
4
5
  export async function server_get_events_by_herd(herd_id) {
5
6
  const supabase = await newServerClient();
6
7
  // fetch events and include devices
@@ -13,8 +14,12 @@ export async function server_get_events_by_herd(herd_id) {
13
14
  `)
14
15
  .eq("devices.herd_id", herd_id)
15
16
  .order("timestamp_observation", { ascending: false });
17
+ // Add signed URLs to events using the same client
18
+ const eventsWithSignedUrls = data
19
+ ? await addSignedUrlsToEvents(data, supabase)
20
+ : [];
16
21
  // TODO: DETERMINE WHEN TO PASS ERROR
17
- let response = IWebResponse.success(data ? data : []);
22
+ let response = IWebResponse.success(eventsWithSignedUrls);
18
23
  return response.to_compatible();
19
24
  }
20
25
  export async function server_get_more_events_by_herd(herd_id, offset, page_count = 10) {
@@ -32,8 +37,12 @@ export async function server_get_more_events_by_herd(herd_id, offset, page_count
32
37
  .eq("devices.herd_id", herd_id)
33
38
  .range(from, to)
34
39
  .order("timestamp_observation", { ascending: false });
40
+ // Add signed URLs to events using the same client
41
+ const eventsWithSignedUrls = data
42
+ ? await addSignedUrlsToEvents(data, supabase)
43
+ : [];
35
44
  // TODO: DETERMINE WHEN TO PASS ERROR
36
- let response = IWebResponse.success(data ? data : []);
45
+ let response = IWebResponse.success(eventsWithSignedUrls);
37
46
  return response.to_compatible();
38
47
  }
39
48
  // function to get total number of events for a herd
@@ -0,0 +1,36 @@
1
+ import { SupabaseClient } from "@supabase/supabase-js";
2
+ import { Database } from "../types/supabase";
3
+ /**
4
+ * Generates a signed URL for a file in Supabase storage
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)
7
+ * @param supabaseClient - Optional Supabase client (will create new one if not provided)
8
+ * @returns Promise<string | null> - The signed URL or null if error
9
+ */
10
+ export declare function generateSignedUrl(filePath: string, expiresIn?: number, supabaseClient?: SupabaseClient<Database>): Promise<string | null>;
11
+ /**
12
+ * Generates signed URLs for multiple events and sets them as media_url
13
+ * @param events - Array of events that may have file_path
14
+ * @param supabaseClient - Optional Supabase client (will create new one if not provided)
15
+ * @returns Promise<Array> - Events with signed URLs set as media_url
16
+ */
17
+ export declare function addSignedUrlsToEvents(events: any[], supabaseClient?: SupabaseClient<Database>): Promise<any[]>;
18
+ /**
19
+ * Generates a signed URL for a single event and sets it as media_url
20
+ * @param event - Event object that may have file_path
21
+ * @param supabaseClient - Optional Supabase client (will create new one if not provided)
22
+ * @returns Promise<Object> - Event with signed URL set as media_url
23
+ */
24
+ export declare function addSignedUrlToEvent(event: any, supabaseClient?: SupabaseClient<Database>): Promise<any>;
25
+ /**
26
+ * Gets the media URL for an event (signed URL if file_path exists, otherwise media_url)
27
+ * @param event - Event object that may have file_path or media_url
28
+ * @returns string | null - The media URL or null if none available
29
+ */
30
+ export declare function getEventMediaUrl(event: any): string | null;
31
+ /**
32
+ * Checks if an event has any media URL available
33
+ * @param event - Event object
34
+ * @returns boolean - True if event has any media URL
35
+ */
36
+ export declare function hasEventMedia(event: any): boolean;
@@ -0,0 +1,82 @@
1
+ "use server";
2
+ import { newServerClient } from "../supabase/server";
3
+ import { BUCKET_NAME_SCOUT } from "../constants/db";
4
+ /**
5
+ * Generates a signed URL for a file in Supabase storage
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)
8
+ * @param supabaseClient - Optional Supabase client (will create new one if not provided)
9
+ * @returns Promise<string | null> - The signed URL or null if error
10
+ */
11
+ export async function generateSignedUrl(filePath, expiresIn = 3600, supabaseClient) {
12
+ try {
13
+ const supabase = supabaseClient || (await newServerClient());
14
+ const { data, error } = await supabase.storage
15
+ .from(BUCKET_NAME_SCOUT)
16
+ .createSignedUrl(filePath, expiresIn);
17
+ if (error) {
18
+ console.error("Error generating signed URL:", error.message);
19
+ return null;
20
+ }
21
+ return data.signedUrl;
22
+ }
23
+ catch (error) {
24
+ console.error("Error in generateSignedUrl:", error);
25
+ return null;
26
+ }
27
+ }
28
+ /**
29
+ * Generates signed URLs for multiple events and sets them as media_url
30
+ * @param events - Array of events that may have file_path
31
+ * @param supabaseClient - Optional Supabase client (will create new one if not provided)
32
+ * @returns Promise<Array> - Events with signed URLs set as media_url
33
+ */
34
+ export async function addSignedUrlsToEvents(events, supabaseClient) {
35
+ const eventsWithSignedUrls = await Promise.all(events.map(async (event) => {
36
+ // If event has a file_path, generate a signed URL and set it as media_url
37
+ if (event.file_path) {
38
+ const signedUrl = await generateSignedUrl(event.file_path, 3600, supabaseClient);
39
+ return {
40
+ ...event,
41
+ media_url: signedUrl || event.media_url, // Fall back to existing media_url if signed URL fails
42
+ };
43
+ }
44
+ // If no file_path, keep existing media_url
45
+ return event;
46
+ }));
47
+ return eventsWithSignedUrls;
48
+ }
49
+ /**
50
+ * Generates a signed URL for a single event and sets it as media_url
51
+ * @param event - Event object that may have file_path
52
+ * @param supabaseClient - Optional Supabase client (will create new one if not provided)
53
+ * @returns Promise<Object> - Event with signed URL set as media_url
54
+ */
55
+ export async function addSignedUrlToEvent(event, supabaseClient) {
56
+ // If event has a file_path, generate a signed URL and set it as media_url
57
+ if (event.file_path) {
58
+ const signedUrl = await generateSignedUrl(event.file_path, 3600, supabaseClient);
59
+ return {
60
+ ...event,
61
+ media_url: signedUrl || event.media_url, // Fall back to existing media_url if signed URL fails
62
+ };
63
+ }
64
+ // If no file_path, keep existing media_url
65
+ return event;
66
+ }
67
+ /**
68
+ * Gets the media URL for an event (signed URL if file_path exists, otherwise media_url)
69
+ * @param event - Event object that may have file_path or media_url
70
+ * @returns string | null - The media URL or null if none available
71
+ */
72
+ export function getEventMediaUrl(event) {
73
+ return event.media_url || null;
74
+ }
75
+ /**
76
+ * Checks if an event has any media URL available
77
+ * @param event - Event object
78
+ * @returns boolean - True if event has any media URL
79
+ */
80
+ export function hasEventMedia(event) {
81
+ return !!(event.media_url || event.file_path);
82
+ }
@@ -2,6 +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
6
  export async function server_create_tags(tags) {
6
7
  const supabase = await newServerClient();
7
8
  // remove id key from tags
@@ -89,7 +90,9 @@ export async function server_get_more_events_with_tags_by_herd(herd_id, offset,
89
90
  event.tags = event.tags.filter((tag) => tag !== null);
90
91
  return event;
91
92
  });
92
- return IWebResponse.success(filtered_data).to_compatible();
93
+ // Add signed URLs to events using the same client
94
+ const eventsWithSignedUrls = await addSignedUrlsToEvents(filtered_data, supabase);
95
+ return IWebResponse.success(eventsWithSignedUrls).to_compatible();
93
96
  }
94
97
  export async function server_get_events_and_tags_for_device(device_id, limit = 3) {
95
98
  const supabase = await newServerClient();
@@ -106,7 +109,9 @@ export async function server_get_events_and_tags_for_device(device_id, limit = 3
106
109
  data: [],
107
110
  };
108
111
  }
109
- return IWebResponse.success(data).to_compatible();
112
+ // Add signed URLs to events using the same client
113
+ const eventsWithSignedUrls = await addSignedUrlsToEvents(data, supabase);
114
+ return IWebResponse.success(eventsWithSignedUrls).to_compatible();
110
115
  }
111
116
  export async function get_event_and_tags_by_event_id(event_id) {
112
117
  const supabase = await newServerClient();
@@ -154,5 +159,7 @@ export async function get_event_and_tags_by_event_id(event_id) {
154
159
  tags: data[0].tags || [],
155
160
  earthranger_url: data[0].earthranger_url,
156
161
  };
157
- return IWebResponse.success(transformedData).to_compatible();
162
+ // Add signed URL to event using the same client
163
+ const eventWithSignedUrl = await addSignedUrlToEvent(transformedData, supabase);
164
+ return IWebResponse.success(eventWithSignedUrl).to_compatible();
158
165
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Hook for generating signed URLs on the client side
3
+ * @param filePath - The file path in storage
4
+ * @param expiresIn - Number of seconds until URL expires (default: 3600 = 1 hour)
5
+ * @returns Object with signedUrl and loading state
6
+ */
7
+ export declare function useSignedUrl(filePath: string | null, expiresIn?: number): {
8
+ signedUrl: string | null;
9
+ loading: boolean;
10
+ error: string | null;
11
+ };
@@ -0,0 +1,50 @@
1
+ "use client";
2
+ import { useState, useEffect } from "react";
3
+ import { createBrowserClient } from "@supabase/ssr";
4
+ import { BUCKET_NAME_EVENTS } from "../constants/db";
5
+ /**
6
+ * Hook for generating signed URLs on the client side
7
+ * @param filePath - The file path in storage
8
+ * @param expiresIn - Number of seconds until URL expires (default: 3600 = 1 hour)
9
+ * @returns Object with signedUrl and loading state
10
+ */
11
+ export function useSignedUrl(filePath, expiresIn = 3600) {
12
+ const [signedUrl, setSignedUrl] = useState(null);
13
+ const [loading, setLoading] = useState(false);
14
+ const [error, setError] = useState(null);
15
+ useEffect(() => {
16
+ if (!filePath) {
17
+ setSignedUrl(null);
18
+ setError(null);
19
+ return;
20
+ }
21
+ const generateSignedUrl = async () => {
22
+ setLoading(true);
23
+ setError(null);
24
+ try {
25
+ const url = process.env.NEXT_PUBLIC_SUPABASE_URL || "";
26
+ const anon_key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "";
27
+ const supabase = createBrowserClient(url, anon_key);
28
+ const { data, error } = await supabase.storage
29
+ .from(BUCKET_NAME_EVENTS)
30
+ .createSignedUrl(filePath, expiresIn);
31
+ if (error) {
32
+ setError(error.message);
33
+ setSignedUrl(null);
34
+ }
35
+ else {
36
+ setSignedUrl(data.signedUrl);
37
+ }
38
+ }
39
+ catch (err) {
40
+ setError(err instanceof Error ? err.message : "Unknown error");
41
+ setSignedUrl(null);
42
+ }
43
+ finally {
44
+ setLoading(false);
45
+ }
46
+ };
47
+ generateSignedUrl();
48
+ }, [filePath, expiresIn]);
49
+ return { signedUrl, loading, error };
50
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./constants/annotator";
2
2
  export * from "./constants/app";
3
+ export * from "./constants/db";
3
4
  export * from "./types/db";
4
5
  export * from "./types/herd_module";
5
6
  export * from "./types/ui";
@@ -23,6 +24,7 @@ export * from "./helpers/ui";
23
24
  export * from "./helpers/users";
24
25
  export * from "./helpers/web";
25
26
  export * from "./helpers/zones";
27
+ export * from "./helpers/storage";
26
28
  export * from "./hooks/useScoutDbListener";
27
29
  export * from "./hooks/useScoutRefresh";
28
30
  export * from "./providers";
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // Constants
2
2
  export * from "./constants/annotator";
3
3
  export * from "./constants/app";
4
+ export * from "./constants/db";
4
5
  // Types
5
6
  export * from "./types/db";
6
7
  export * from "./types/herd_module";
@@ -26,6 +27,7 @@ export * from "./helpers/ui";
26
27
  export * from "./helpers/users";
27
28
  export * from "./helpers/web";
28
29
  export * from "./helpers/zones";
30
+ export * from "./helpers/storage";
29
31
  // Hooks
30
32
  export * from "./hooks/useScoutDbListener";
31
33
  export * from "./hooks/useScoutRefresh";
@@ -102,13 +102,14 @@ export declare function newServerClient(): Promise<import("@supabase/supabase-js
102
102
  altitude: number;
103
103
  device_id: number;
104
104
  earthranger_url: string | null;
105
+ file_path: string | null;
105
106
  heading: number;
106
107
  id: number;
107
108
  inserted_at: string;
108
109
  is_public: boolean;
109
110
  location: unknown | null;
110
111
  media_type: Database["public"]["Enums"]["media_type"];
111
- media_url: string;
112
+ media_url: string | null;
112
113
  message: string;
113
114
  timestamp_observation: string;
114
115
  };
@@ -116,13 +117,14 @@ export declare function newServerClient(): Promise<import("@supabase/supabase-js
116
117
  altitude: number;
117
118
  device_id: number;
118
119
  earthranger_url?: string | null;
120
+ file_path?: string | null;
119
121
  heading: number;
120
122
  id?: number;
121
123
  inserted_at?: string;
122
124
  is_public?: boolean;
123
125
  location?: unknown | null;
124
126
  media_type?: Database["public"]["Enums"]["media_type"];
125
- media_url: string;
127
+ media_url?: string | null;
126
128
  message: string;
127
129
  timestamp_observation?: string;
128
130
  };
@@ -130,13 +132,14 @@ export declare function newServerClient(): Promise<import("@supabase/supabase-js
130
132
  altitude?: number;
131
133
  device_id?: number;
132
134
  earthranger_url?: string | null;
135
+ file_path?: string | null;
133
136
  heading?: number;
134
137
  id?: number;
135
138
  inserted_at?: string;
136
139
  is_public?: boolean;
137
140
  location?: unknown | null;
138
141
  media_type?: Database["public"]["Enums"]["media_type"];
139
- media_url?: string;
142
+ media_url?: string | null;
140
143
  message?: string;
141
144
  timestamp_observation?: string;
142
145
  };
@@ -362,6 +365,7 @@ export declare function newServerClient(): Promise<import("@supabase/supabase-js
362
365
  altitude: number | null;
363
366
  device_id: number | null;
364
367
  earthranger_url: string | null;
368
+ file_path: string | null;
365
369
  heading: number | null;
366
370
  herd_id: number | null;
367
371
  id: number | null;
@@ -497,13 +501,14 @@ export declare function newServerClient(): Promise<import("@supabase/supabase-js
497
501
  altitude: number;
498
502
  device_id: number;
499
503
  earthranger_url: string | null;
504
+ file_path: string | null;
500
505
  heading: number;
501
506
  id: number;
502
507
  inserted_at: string;
503
508
  is_public: boolean;
504
509
  location: unknown | null;
505
510
  media_type: Database["public"]["Enums"]["media_type"];
506
- media_url: string;
511
+ media_url: string | null;
507
512
  message: string;
508
513
  timestamp_observation: string;
509
514
  }[];
package/dist/types/db.js CHANGED
@@ -12,4 +12,5 @@ export const DUMMY_EVENT = {
12
12
  is_public: true,
13
13
  timestamp_observation: new Date().toISOString(),
14
14
  earthranger_url: null,
15
+ file_path: null,
15
16
  };
@@ -136,13 +136,14 @@ export type Database = {
136
136
  altitude: number;
137
137
  device_id: number;
138
138
  earthranger_url: string | null;
139
+ file_path: string | null;
139
140
  heading: number;
140
141
  id: number;
141
142
  inserted_at: string;
142
143
  is_public: boolean;
143
144
  location: unknown | null;
144
145
  media_type: Database["public"]["Enums"]["media_type"];
145
- media_url: string;
146
+ media_url: string | null;
146
147
  message: string;
147
148
  timestamp_observation: string;
148
149
  };
@@ -150,13 +151,14 @@ export type Database = {
150
151
  altitude: number;
151
152
  device_id: number;
152
153
  earthranger_url?: string | null;
154
+ file_path?: string | null;
153
155
  heading: number;
154
156
  id?: number;
155
157
  inserted_at?: string;
156
158
  is_public?: boolean;
157
159
  location?: unknown | null;
158
160
  media_type?: Database["public"]["Enums"]["media_type"];
159
- media_url: string;
161
+ media_url?: string | null;
160
162
  message: string;
161
163
  timestamp_observation?: string;
162
164
  };
@@ -164,13 +166,14 @@ export type Database = {
164
166
  altitude?: number;
165
167
  device_id?: number;
166
168
  earthranger_url?: string | null;
169
+ file_path?: string | null;
167
170
  heading?: number;
168
171
  id?: number;
169
172
  inserted_at?: string;
170
173
  is_public?: boolean;
171
174
  location?: unknown | null;
172
175
  media_type?: Database["public"]["Enums"]["media_type"];
173
- media_url?: string;
176
+ media_url?: string | null;
174
177
  message?: string;
175
178
  timestamp_observation?: string;
176
179
  };
@@ -410,6 +413,7 @@ export type Database = {
410
413
  altitude: number | null;
411
414
  device_id: number | null;
412
415
  earthranger_url: string | null;
416
+ file_path: string | null;
413
417
  heading: number | null;
414
418
  herd_id: number | null;
415
419
  id: number | null;
@@ -550,13 +554,14 @@ export type Database = {
550
554
  altitude: number;
551
555
  device_id: number;
552
556
  earthranger_url: string | null;
557
+ file_path: string | null;
553
558
  heading: number;
554
559
  id: number;
555
560
  inserted_at: string;
556
561
  is_public: boolean;
557
562
  location: unknown | null;
558
563
  media_type: Database["public"]["Enums"]["media_type"];
559
- media_url: string;
564
+ media_url: string | null;
560
565
  message: string;
561
566
  timestamp_observation: string;
562
567
  }[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adventurelabs/scout-core",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
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",