@adventurelabs/scout-core 1.4.83 → 1.4.85

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,6 +1,7 @@
1
1
  import { IApiKeyScout } from "../types/db";
2
+ import { SupabaseClient } from "@supabase/supabase-js";
2
3
  export declare function test_api_key_loading(device_id: number): Promise<boolean>;
3
4
  export declare function server_list_api_keys(device_id: number): Promise<IApiKeyScout[]>;
4
- export declare function server_list_api_keys_batch(device_ids: number[]): Promise<{
5
+ export declare function server_list_api_keys_batch(device_ids: number[], client?: SupabaseClient): Promise<{
5
6
  [device_id: number]: IApiKeyScout[];
6
7
  }>;
@@ -31,8 +31,8 @@ export async function server_list_api_keys(device_id) {
31
31
  }
32
32
  return data_to_return;
33
33
  }
34
- export async function server_list_api_keys_batch(device_ids) {
35
- const supabase = await newServerClient();
34
+ export async function server_list_api_keys_batch(device_ids, client) {
35
+ const supabase = client || (await newServerClient());
36
36
  // Check if the batch function exists by trying a simple call
37
37
  try {
38
38
  const { data, error } = await supabase.rpc("load_api_keys_batch", {
@@ -0,0 +1,9 @@
1
+ import { Database } from "../types/supabase";
2
+ import { CredentialQueryArgs, IHerdCredential, HerdCredentialInsert, HerdCredentialUpdate } from "../types/db";
3
+ import { IWebResponseCompatible } from "../types/requests";
4
+ import { SupabaseClient } from "@supabase/supabase-js";
5
+ export declare function get_herd_credentials_by_herd_id(client: SupabaseClient<Database>, herd_id: number, args?: CredentialQueryArgs): Promise<IWebResponseCompatible<IHerdCredential[]>>;
6
+ export declare function get_herd_credential_by_id(client: SupabaseClient<Database>, credential_id: number): Promise<IWebResponseCompatible<IHerdCredential | null>>;
7
+ export declare function create_herd_credential(client: SupabaseClient<Database>, row: HerdCredentialInsert): Promise<IWebResponseCompatible<IHerdCredential | null>>;
8
+ export declare function update_herd_credential(client: SupabaseClient<Database>, credential_id: number, patch: HerdCredentialUpdate): Promise<IWebResponseCompatible<IHerdCredential | null>>;
9
+ export declare function delete_herd_credential(client: SupabaseClient<Database>, credential_id: number): Promise<IWebResponseCompatible<IHerdCredential | null>>;
@@ -0,0 +1,85 @@
1
+ import { IWebResponse } from "../types/requests";
2
+ export async function get_herd_credentials_by_herd_id(client, herd_id, args) {
3
+ let query = client
4
+ .from("herd_credentials")
5
+ .select("*")
6
+ .eq("herd_id", herd_id);
7
+ if (args?.type)
8
+ query = query.eq("type", args.type);
9
+ const { data, error } = await query.order("created_at", { ascending: false });
10
+ if (error) {
11
+ return IWebResponse.error(error.message).to_compatible();
12
+ }
13
+ return IWebResponse.success(data ?? []).to_compatible();
14
+ }
15
+ export async function get_herd_credential_by_id(client, credential_id) {
16
+ const { data, error } = await client
17
+ .from("herd_credentials")
18
+ .select("*")
19
+ .eq("id", credential_id)
20
+ .single();
21
+ if (error) {
22
+ return IWebResponse.error(error.message).to_compatible();
23
+ }
24
+ if (!data) {
25
+ return IWebResponse.error("Herd credential not found").to_compatible();
26
+ }
27
+ return IWebResponse.success(data).to_compatible();
28
+ }
29
+ export async function create_herd_credential(client, row) {
30
+ if (!row.herd_id) {
31
+ return IWebResponse.error("herd_id is required").to_compatible();
32
+ }
33
+ if (!row.issuer?.trim()) {
34
+ return IWebResponse.error("issuer is required").to_compatible();
35
+ }
36
+ if (!row.type?.trim()) {
37
+ return IWebResponse.error("type is required").to_compatible();
38
+ }
39
+ const { data, error } = await client
40
+ .from("herd_credentials")
41
+ .insert([row])
42
+ .select("*")
43
+ .single();
44
+ if (error) {
45
+ return IWebResponse.error(error.message).to_compatible();
46
+ }
47
+ if (!data) {
48
+ return IWebResponse.error("Failed to create herd credential").to_compatible();
49
+ }
50
+ return IWebResponse.success(data).to_compatible();
51
+ }
52
+ export async function update_herd_credential(client, credential_id, patch) {
53
+ const { id: _id, herd_id: _hid, created_at: _ca, ...updateData } = patch;
54
+ if (Object.keys(updateData).length === 0) {
55
+ return IWebResponse.error("No valid fields to update").to_compatible();
56
+ }
57
+ const { data, error } = await client
58
+ .from("herd_credentials")
59
+ .update(updateData)
60
+ .eq("id", credential_id)
61
+ .select("*")
62
+ .single();
63
+ if (error) {
64
+ return IWebResponse.error(error.message).to_compatible();
65
+ }
66
+ if (!data) {
67
+ return IWebResponse.error("Herd credential not found").to_compatible();
68
+ }
69
+ return IWebResponse.success(data).to_compatible();
70
+ }
71
+ export async function delete_herd_credential(client, credential_id) {
72
+ const { data, error } = await client
73
+ .from("herd_credentials")
74
+ .delete()
75
+ .eq("id", credential_id)
76
+ .select("*")
77
+ .single();
78
+ if (error) {
79
+ return IWebResponse.error(error.message).to_compatible();
80
+ }
81
+ if (!data) {
82
+ return IWebResponse.error("Herd credential not found").to_compatible();
83
+ }
84
+ return IWebResponse.success(data).to_compatible();
85
+ }
@@ -1,7 +1,18 @@
1
1
  "use server";
2
+ import { createHash } from "crypto";
2
3
  import { newServerClient } from "../supabase/server";
3
4
  import { EnumWebResponse, IWebResponse, } from "../types/requests";
4
- import { HerdModule, } from "../types/herd_module";
5
+ import { LABELS } from "../constants/annotator";
6
+ import { get_devices_by_herd } from "./devices";
7
+ import { server_get_plans_by_herd_ids } from "./plans";
8
+ import { server_get_layers_by_herd_ids } from "./layers";
9
+ import { server_get_providers_by_herd_ids } from "./providers";
10
+ import { server_get_users_with_herd_access_batch } from "./users";
11
+ import { get_parts_by_herd_ids } from "./parts";
12
+ import { server_get_more_zones_and_actions_for_herd } from "./zones";
13
+ import { server_list_api_keys_batch } from "../api_keys/actions";
14
+ import { server_get_session_summaries_by_herd } from "./session_summaries";
15
+ import { server_get_session_usage_over_time_by_herd } from "./sessions";
5
16
  export async function get_herds(client) {
6
17
  const { data: herds } = await client.from("herds").select();
7
18
  if (!herds) {
@@ -45,7 +56,7 @@ export async function get_herd_by_slug(slug) {
45
56
  }
46
57
  export async function deleteHerd(herd_id) {
47
58
  const supabase = await newServerClient();
48
- const { error } = await supabase.from("herds").delete().match({ id: 20 });
59
+ const { error } = await supabase.from("herds").delete().match({ id: herd_id });
49
60
  if (error) {
50
61
  return {
51
62
  status: EnumWebResponse.ERROR,
@@ -71,12 +82,17 @@ export async function createHerd(newHerd) {
71
82
  return IWebResponse.success(true).to_compatible();
72
83
  }
73
84
  }
85
+ // Stable hash of modules (queries return rows in a deterministic DB order), minus
86
+ // the per-fetch timestamp, so the client can skip no-op updates without a deep walk.
87
+ function hashHerdModules(modules) {
88
+ const stable = modules.map(({ timestamp_last_refreshed: _ts, ...rest }) => rest);
89
+ return createHash("sha1").update(JSON.stringify(stable)).digest("hex");
90
+ }
74
91
  export async function server_load_herd_modules() {
75
92
  const startTime = Date.now();
76
- const client_supabase = await newServerClient();
77
- const herdsResult = await get_herds_with_location(client_supabase);
78
- if (herdsResult.status !== EnumWebResponse.SUCCESS ||
79
- !herdsResult.data) {
93
+ const client = await newServerClient();
94
+ const herdsResult = await get_herds_with_location(client);
95
+ if (herdsResult.status !== EnumWebResponse.SUCCESS || !herdsResult.data) {
80
96
  return {
81
97
  status: EnumWebResponse.ERROR,
82
98
  msg: herdsResult.msg ?? "Failed to load herds",
@@ -84,26 +100,88 @@ export async function server_load_herd_modules() {
84
100
  time_finished: Date.now(),
85
101
  time_sent: Date.now(),
86
102
  server_processing_time_ms: Date.now() - startTime,
103
+ content_hash: "",
87
104
  };
88
105
  }
89
106
  const herds = herdsResult.data.filter((h) => h.id != null);
90
- const endTime = Date.now();
91
- const totalLoadTime = endTime - startTime;
92
107
  if (herds.length === 0) {
93
- console.log(`[server_load_herd_modules] No accessible herds (${totalLoadTime}ms)`);
108
+ const endTime = Date.now();
109
+ console.log(`[server_load_herd_modules] No accessible herds (${endTime - startTime}ms)`);
94
110
  return {
95
111
  status: EnumWebResponse.SUCCESS,
96
112
  msg: "No herds accessible",
97
113
  data: [],
98
114
  time_finished: endTime,
99
115
  time_sent: endTime,
100
- server_processing_time_ms: totalLoadTime,
116
+ server_processing_time_ms: endTime - startTime,
117
+ content_hash: hashHerdModules([]),
101
118
  };
102
119
  }
103
- const herdModulePromises = herds.map((herd) => HerdModule.from_herd(herd, client_supabase));
104
- const new_herd_modules = await Promise.all(herdModulePromises);
105
- const serialized_herd_modules = new_herd_modules.map((herd_module) => herd_module.to_serializable());
106
- console.log(`[server_load_herd_modules] Loaded ${herds.length} herds in ${totalLoadTime}ms (parallel processing)`);
120
+ const herdIds = herds.map((h) => h.id);
121
+ // Run a per-herd RPC across all herds in parallel, tolerating individual failures.
122
+ const perHerd = (fn) => Promise.all(herds.map((h) => fn(h.id).catch(() => ({
123
+ status: EnumWebResponse.ERROR,
124
+ msg: "",
125
+ data: null,
126
+ }))));
127
+ const grouped = (p) => p.then((r) => r.data ?? {}).catch(() => ({}));
128
+ const [plansByHerd, layersByHerd, providersByHerd, usersByHerd, partsByDevice, devicesResults, zonesResults, summariesResults, usageResults,] = await Promise.all([
129
+ grouped(server_get_plans_by_herd_ids(herdIds, client)),
130
+ grouped(server_get_layers_by_herd_ids(herdIds, client)),
131
+ grouped(server_get_providers_by_herd_ids(herdIds, client)),
132
+ server_get_users_with_herd_access_batch(herdIds, client)
133
+ .then((r) => (r.status === EnumWebResponse.SUCCESS ? r.data : null))
134
+ .catch(() => null),
135
+ grouped(get_parts_by_herd_ids(client, herdIds)),
136
+ perHerd((id) => get_devices_by_herd(id, client)),
137
+ perHerd((id) => server_get_more_zones_and_actions_for_herd(id, 0, 10, client)),
138
+ perHerd((id) => server_get_session_summaries_by_herd(id, client)),
139
+ perHerd((id) => server_get_session_usage_over_time_by_herd(id, client)),
140
+ ]);
141
+ const devicesByHerdIndex = devicesResults.map((res) => res.status === EnumWebResponse.SUCCESS && res.data ? res.data : []);
142
+ const allDevices = devicesByHerdIndex.flat();
143
+ const allDeviceIds = allDevices.map((device) => device.id ?? 0);
144
+ let apiKeysByDevice = {};
145
+ if (allDeviceIds.length > 0) {
146
+ try {
147
+ apiKeysByDevice = await server_list_api_keys_batch(allDeviceIds, client);
148
+ }
149
+ catch (error) {
150
+ console.error(`[server_load_herd_modules] Failed to load API keys:`, error);
151
+ }
152
+ }
153
+ for (const device of allDevices) {
154
+ const deviceId = device.id ?? 0;
155
+ device.api_keys_scout = apiKeysByDevice[deviceId] || [];
156
+ device.parts = partsByDevice[deviceId] || [];
157
+ }
158
+ const serialized_herd_modules = herds.map((herd, index) => {
159
+ const zonesRes = zonesResults[index];
160
+ const summariesRes = summariesResults[index];
161
+ const usageRes = usageResults[index];
162
+ return {
163
+ herd,
164
+ devices: devicesByHerdIndex[index],
165
+ timestamp_last_refreshed: Date.now(),
166
+ user_roles: usersByHerd ? usersByHerd[herd.id] ?? [] : null,
167
+ labels: LABELS,
168
+ plans: plansByHerd[herd.id] ?? [],
169
+ zones: zonesRes.status === EnumWebResponse.SUCCESS && zonesRes.data
170
+ ? zonesRes.data
171
+ : [],
172
+ layers: layersByHerd[herd.id] ?? [],
173
+ providers: providersByHerd[herd.id] ?? [],
174
+ session_summaries: summariesRes.status === EnumWebResponse.SUCCESS && summariesRes.data
175
+ ? summariesRes.data
176
+ : null,
177
+ session_usage: usageRes.status === EnumWebResponse.SUCCESS && usageRes.data
178
+ ? usageRes.data
179
+ : null,
180
+ };
181
+ });
182
+ const endTime = Date.now();
183
+ const totalLoadTime = endTime - startTime;
184
+ console.log(`[server_load_herd_modules] Loaded ${herds.length} herds in ${totalLoadTime}ms (batched queries)`);
107
185
  return {
108
186
  status: EnumWebResponse.SUCCESS,
109
187
  msg: "Herd modules loaded successfully",
@@ -111,5 +189,6 @@ export async function server_load_herd_modules() {
111
189
  time_finished: endTime,
112
190
  time_sent: endTime,
113
191
  server_processing_time_ms: totalLoadTime,
192
+ content_hash: hashHerdModules(serialized_herd_modules),
114
193
  };
115
194
  }
@@ -3,7 +3,8 @@ export * from "./auth";
3
3
  export * from "./bounding_boxes";
4
4
  export * from "./chat";
5
5
  export * from "./certificates";
6
- export * from "./credentials";
6
+ export * from "./user_credentials";
7
+ export * from "./herd_credentials";
7
8
  export * from "./db";
8
9
  export * from "./devices";
9
10
  export * from "./email";
@@ -3,7 +3,8 @@ export * from "./auth";
3
3
  export * from "./bounding_boxes";
4
4
  export * from "./chat";
5
5
  export * from "./certificates";
6
- export * from "./credentials";
6
+ export * from "./user_credentials";
7
+ export * from "./herd_credentials";
7
8
  export * from "./db";
8
9
  export * from "./devices";
9
10
  export * from "./email";
@@ -1,3 +1,6 @@
1
+ import { Database } from "../types/supabase";
1
2
  import { ILayer } from "../types/db";
2
3
  import { IWebResponseCompatible } from "../types/requests";
4
+ import { SupabaseClient } from "@supabase/supabase-js";
3
5
  export declare function server_get_layers_by_herd(herd_id: number): Promise<IWebResponseCompatible<ILayer[]>>;
6
+ export declare function server_get_layers_by_herd_ids(herd_ids: number[], client: SupabaseClient<Database>): Promise<IWebResponseCompatible<Record<number, ILayer[]>>>;
@@ -19,3 +19,23 @@ export async function server_get_layers_by_herd(herd_id) {
19
19
  return IWebResponse.success(data).to_compatible();
20
20
  }
21
21
  }
22
+ // Fetches layers for many herds in one query, grouped by herd_id.
23
+ export async function server_get_layers_by_herd_ids(herd_ids, client) {
24
+ var _a;
25
+ if (herd_ids.length === 0) {
26
+ return IWebResponse.success({}).to_compatible();
27
+ }
28
+ const { data, error } = await client
29
+ .from("layers")
30
+ .select("*")
31
+ .in("herd_id", herd_ids)
32
+ .order("id", { ascending: true });
33
+ if (error) {
34
+ return { status: EnumWebResponse.ERROR, msg: error.message, data: null };
35
+ }
36
+ const grouped = {};
37
+ for (const row of data ?? []) {
38
+ (grouped[_a = row.herd_id] ?? (grouped[_a] = [])).push(row);
39
+ }
40
+ return IWebResponse.success(grouped).to_compatible();
41
+ }
@@ -75,6 +75,13 @@ export declare function get_parts_by_certificate_id(client: SupabaseClient<Datab
75
75
  * @param herd_id - ID of the herd to get parts for
76
76
  */
77
77
  export declare function get_parts_by_herd_id(client: SupabaseClient<Database>, herd_id: number): Promise<IWebResponseCompatible<IPart[]>>;
78
+ /**
79
+ * Retrieves active parts for all devices across many herds in one query,
80
+ * grouped by device_id.
81
+ * @param client - Supabase client instance
82
+ * @param herd_ids - IDs of the herds to get parts for
83
+ */
84
+ export declare function get_parts_by_herd_ids(client: SupabaseClient<Database>, herd_ids: number[]): Promise<IWebResponseCompatible<Record<number, IPart[]>>>;
78
85
  /**
79
86
  * Restores a deleted part to active lifecycle
80
87
  * @param client - Supabase client instance
@@ -238,6 +238,38 @@ export async function get_parts_by_herd_id(client, herd_id) {
238
238
  }
239
239
  return IWebResponse.success(data).to_compatible();
240
240
  }
241
+ /**
242
+ * Retrieves active parts for all devices across many herds in one query,
243
+ * grouped by device_id.
244
+ * @param client - Supabase client instance
245
+ * @param herd_ids - IDs of the herds to get parts for
246
+ */
247
+ export async function get_parts_by_herd_ids(client, herd_ids) {
248
+ var _a;
249
+ if (herd_ids.length === 0) {
250
+ return IWebResponse.success({}).to_compatible();
251
+ }
252
+ const { data, error } = await client
253
+ .from("parts")
254
+ .select(`
255
+ *,
256
+ devices!parts_device_id_fkey(herd_id)
257
+ `)
258
+ .in("devices.herd_id", herd_ids)
259
+ .eq("lifecycle", "active")
260
+ .order("created_at", { ascending: false })
261
+ .order("id", { ascending: true });
262
+ if (error) {
263
+ return IWebResponse.error(error.message).to_compatible();
264
+ }
265
+ const grouped = {};
266
+ for (const row of data ?? []) {
267
+ if (row.device_id == null)
268
+ continue;
269
+ (grouped[_a = row.device_id] ?? (grouped[_a] = [])).push(row);
270
+ }
271
+ return IWebResponse.success(grouped).to_compatible();
272
+ }
241
273
  /**
242
274
  * Restores a deleted part to active lifecycle
243
275
  * @param client - Supabase client instance
@@ -1,5 +1,8 @@
1
+ import { Database } from "../types/supabase";
1
2
  import { IPlan } from "../types/db";
2
3
  import { IWebResponseCompatible } from "../types/requests";
4
+ import { SupabaseClient } from "@supabase/supabase-js";
3
5
  export declare function server_get_plans_by_herd(herd_id: number): Promise<IWebResponseCompatible<IPlan[]>>;
6
+ export declare function server_get_plans_by_herd_ids(herd_ids: number[], client: SupabaseClient<Database>): Promise<IWebResponseCompatible<Record<number, IPlan[]>>>;
4
7
  export declare function server_create_plans(plans: IPlan[]): Promise<IWebResponseCompatible<IPlan[]>>;
5
8
  export declare function server_delete_plans_by_ids(plan_ids: number[]): Promise<IWebResponseCompatible<boolean>>;
@@ -19,6 +19,26 @@ export async function server_get_plans_by_herd(herd_id) {
19
19
  return IWebResponse.success(data).to_compatible();
20
20
  }
21
21
  }
22
+ // Fetches plans for many herds in one query, grouped by herd_id.
23
+ export async function server_get_plans_by_herd_ids(herd_ids, client) {
24
+ var _a;
25
+ if (herd_ids.length === 0) {
26
+ return IWebResponse.success({}).to_compatible();
27
+ }
28
+ const { data, error } = await client
29
+ .from("plans")
30
+ .select("*")
31
+ .in("herd_id", herd_ids)
32
+ .order("id", { ascending: true });
33
+ if (error) {
34
+ return { status: EnumWebResponse.ERROR, msg: error.message, data: null };
35
+ }
36
+ const grouped = {};
37
+ for (const row of data ?? []) {
38
+ (grouped[_a = row.herd_id] ?? (grouped[_a] = [])).push(row);
39
+ }
40
+ return IWebResponse.success(grouped).to_compatible();
41
+ }
22
42
  // function that uploads plan to our db
23
43
  export async function server_create_plans(plans) {
24
44
  // loop through plans and format
@@ -1,6 +1,9 @@
1
+ import { Database } from "../types/supabase";
1
2
  import { IProvider } from "../types/db";
2
3
  import { IWebResponseCompatible } from "../types/requests";
4
+ import { SupabaseClient } from "@supabase/supabase-js";
3
5
  export declare function server_get_providers_by_herd(herd_id: number): Promise<IWebResponseCompatible<IProvider[]>>;
6
+ export declare function server_get_providers_by_herd_ids(herd_ids: number[], client: SupabaseClient<Database>): Promise<IWebResponseCompatible<Record<number, IProvider[]>>>;
4
7
  export declare function server_create_provider(provider: Omit<IProvider, "id" | "created_at">): Promise<IWebResponseCompatible<IProvider>>;
5
8
  export declare function server_update_provider(provider_id: number, updates: Partial<Omit<IProvider, "id" | "created_at">>): Promise<IWebResponseCompatible<IProvider>>;
6
9
  export declare function server_delete_providers_by_ids(provider_ids: number[]): Promise<IWebResponseCompatible<boolean>>;
@@ -19,6 +19,26 @@ export async function server_get_providers_by_herd(herd_id) {
19
19
  return IWebResponse.success(data).to_compatible();
20
20
  }
21
21
  }
22
+ // Fetches providers for many herds in one query, grouped by herd_id.
23
+ export async function server_get_providers_by_herd_ids(herd_ids, client) {
24
+ var _a;
25
+ if (herd_ids.length === 0) {
26
+ return IWebResponse.success({}).to_compatible();
27
+ }
28
+ const { data, error } = await client
29
+ .from("providers")
30
+ .select("*")
31
+ .in("herd_id", herd_ids)
32
+ .order("id", { ascending: true });
33
+ if (error) {
34
+ return { status: EnumWebResponse.ERROR, msg: error.message, data: null };
35
+ }
36
+ const grouped = {};
37
+ for (const row of data ?? []) {
38
+ (grouped[_a = row.herd_id] ?? (grouped[_a] = [])).push(row);
39
+ }
40
+ return IWebResponse.success(grouped).to_compatible();
41
+ }
22
42
  // function that creates a new provider in our db
23
43
  export async function server_create_provider(provider) {
24
44
  const supabase = await newServerClient();
@@ -0,0 +1,9 @@
1
+ import { Database } from "../types/supabase";
2
+ import { CredentialQueryArgs, IUserCredential, UserCredentialInsert, UserCredentialUpdate } from "../types/db";
3
+ import { IWebResponseCompatible } from "../types/requests";
4
+ import { SupabaseClient } from "@supabase/supabase-js";
5
+ export declare function get_user_credentials_by_user_id(client: SupabaseClient<Database>, user_id: string, args?: CredentialQueryArgs): Promise<IWebResponseCompatible<IUserCredential[]>>;
6
+ export declare function get_user_credential_by_id(client: SupabaseClient<Database>, credential_id: number): Promise<IWebResponseCompatible<IUserCredential | null>>;
7
+ export declare function create_user_credential(client: SupabaseClient<Database>, row: UserCredentialInsert): Promise<IWebResponseCompatible<IUserCredential | null>>;
8
+ export declare function update_user_credential(client: SupabaseClient<Database>, credential_id: number, patch: UserCredentialUpdate): Promise<IWebResponseCompatible<IUserCredential | null>>;
9
+ export declare function delete_user_credential(client: SupabaseClient<Database>, credential_id: number): Promise<IWebResponseCompatible<IUserCredential | null>>;
@@ -0,0 +1,85 @@
1
+ import { IWebResponse } from "../types/requests";
2
+ export async function get_user_credentials_by_user_id(client, user_id, args) {
3
+ let query = client
4
+ .from("user_credentials")
5
+ .select("*")
6
+ .eq("user_id", user_id);
7
+ if (args?.type)
8
+ query = query.eq("type", args.type);
9
+ const { data, error } = await query.order("created_at", { ascending: false });
10
+ if (error) {
11
+ return IWebResponse.error(error.message).to_compatible();
12
+ }
13
+ return IWebResponse.success(data ?? []).to_compatible();
14
+ }
15
+ export async function get_user_credential_by_id(client, credential_id) {
16
+ const { data, error } = await client
17
+ .from("user_credentials")
18
+ .select("*")
19
+ .eq("id", credential_id)
20
+ .single();
21
+ if (error) {
22
+ return IWebResponse.error(error.message).to_compatible();
23
+ }
24
+ if (!data) {
25
+ return IWebResponse.error("User credential not found").to_compatible();
26
+ }
27
+ return IWebResponse.success(data).to_compatible();
28
+ }
29
+ export async function create_user_credential(client, row) {
30
+ if (!row.user_id) {
31
+ return IWebResponse.error("user_id is required").to_compatible();
32
+ }
33
+ if (!row.issuer?.trim()) {
34
+ return IWebResponse.error("issuer is required").to_compatible();
35
+ }
36
+ if (!row.type?.trim()) {
37
+ return IWebResponse.error("type is required").to_compatible();
38
+ }
39
+ const { data, error } = await client
40
+ .from("user_credentials")
41
+ .insert([row])
42
+ .select("*")
43
+ .single();
44
+ if (error) {
45
+ return IWebResponse.error(error.message).to_compatible();
46
+ }
47
+ if (!data) {
48
+ return IWebResponse.error("Failed to create user credential").to_compatible();
49
+ }
50
+ return IWebResponse.success(data).to_compatible();
51
+ }
52
+ export async function update_user_credential(client, credential_id, patch) {
53
+ const { id: _id, user_id: _uid, created_at: _ca, ...updateData } = patch;
54
+ if (Object.keys(updateData).length === 0) {
55
+ return IWebResponse.error("No valid fields to update").to_compatible();
56
+ }
57
+ const { data, error } = await client
58
+ .from("user_credentials")
59
+ .update(updateData)
60
+ .eq("id", credential_id)
61
+ .select("*")
62
+ .single();
63
+ if (error) {
64
+ return IWebResponse.error(error.message).to_compatible();
65
+ }
66
+ if (!data) {
67
+ return IWebResponse.error("User credential not found").to_compatible();
68
+ }
69
+ return IWebResponse.success(data).to_compatible();
70
+ }
71
+ export async function delete_user_credential(client, credential_id) {
72
+ const { data, error } = await client
73
+ .from("user_credentials")
74
+ .delete()
75
+ .eq("id", credential_id)
76
+ .select("*")
77
+ .single();
78
+ if (error) {
79
+ return IWebResponse.error(error.message).to_compatible();
80
+ }
81
+ if (!data) {
82
+ return IWebResponse.error("User credential not found").to_compatible();
83
+ }
84
+ return IWebResponse.success(data).to_compatible();
85
+ }
@@ -5,6 +5,7 @@ import { Database } from "../types/supabase";
5
5
  export declare function server_get_user_roles(herd_id: number): Promise<IWebResponseCompatible<IUserRolePerHerd[]>>;
6
6
  export declare function server_get_user(): Promise<IWebResponseCompatible<IUser | null>>;
7
7
  export declare function server_get_users_with_herd_access(herd_id: number, supabaseClient?: SupabaseClient): Promise<IWebResponseCompatible<IUserAndRole[]>>;
8
+ export declare function server_get_users_with_herd_access_batch(herd_ids: number[], client: SupabaseClient): Promise<IWebResponseCompatible<Record<number, IUserAndRole[]>>>;
8
9
  export declare function server_upsert_user_herd_role(herd_id: number, username: string, herd_role_id: number): Promise<IWebResponseCompatible<IUserAndRole | null>>;
9
10
  export type UserProfileUpdate = Database["public"]["Tables"]["users"]["Update"];
10
11
  export declare function update_user_profile(client: SupabaseClient<Database>, user_id: string, patch: UserProfileUpdate): Promise<IWebResponseCompatible<IUserProfileRow | null>>;
@@ -49,6 +49,36 @@ export async function server_get_users_with_herd_access(herd_id, supabaseClient)
49
49
  }));
50
50
  return IWebResponse.success(transformedData).to_compatible();
51
51
  }
52
+ // Fetches users-with-role for many herds in one query, grouped by herd_id.
53
+ export async function server_get_users_with_herd_access_batch(herd_ids, client) {
54
+ var _a;
55
+ if (herd_ids.length === 0) {
56
+ return IWebResponse.success({}).to_compatible();
57
+ }
58
+ const { data, error } = await client
59
+ .from("users_roles_per_herd")
60
+ .select(`
61
+ herd_id,
62
+ herd_role_id,
63
+ herd_roles (*),
64
+ users!users_roles_per_herd_user_id_fkey (*)
65
+ `)
66
+ .in("herd_id", herd_ids)
67
+ .order("herd_id", { ascending: true })
68
+ .order("user_id", { ascending: true });
69
+ if (error) {
70
+ return { status: EnumWebResponse.ERROR, msg: error.message, data: null };
71
+ }
72
+ const grouped = {};
73
+ for (const item of (data ?? [])) {
74
+ (grouped[_a = item.herd_id] ?? (grouped[_a] = [])).push({
75
+ user: item.users,
76
+ herd_role_id: item.herd_role_id,
77
+ herd_role: item.herd_roles,
78
+ });
79
+ }
80
+ return IWebResponse.success(grouped).to_compatible();
81
+ }
52
82
  export async function server_upsert_user_herd_role(herd_id, username, herd_role_id) {
53
83
  const supabase = await newServerClient();
54
84
  const { data: user, error: user_error } = await supabase
@@ -1,5 +1,6 @@
1
1
  import { IZoneWithActions } from "../types/db";
2
2
  import { IWebResponseCompatible } from "../types/requests";
3
+ import { SupabaseClient } from "@supabase/supabase-js";
3
4
  /**
4
5
  * Get more zones and actions for a herd
5
6
  * @param herd_id - The ID of the herd to get zones and actions for
@@ -8,7 +9,7 @@ import { IWebResponseCompatible } from "../types/requests";
8
9
  * @returns A list of zones and actions
9
10
  * @throws An error if the zones or actions are not fetched
10
11
  */
11
- export declare function server_get_more_zones_and_actions_for_herd(herd_id: number, offset: number, page_count?: number): Promise<IWebResponseCompatible<IZoneWithActions[]>>;
12
+ export declare function server_get_more_zones_and_actions_for_herd(herd_id: number, offset: number, page_count?: number, client?: SupabaseClient): Promise<IWebResponseCompatible<IZoneWithActions[]>>;
12
13
  /**
13
14
  * Create zones and actions for a herd
14
15
  * @param zones - The zones to create
@@ -8,10 +8,10 @@ import { EnumWebResponse, IWebResponse, } from "../types/requests";
8
8
  * @returns A list of zones and actions
9
9
  * @throws An error if the zones or actions are not fetched
10
10
  */
11
- export async function server_get_more_zones_and_actions_for_herd(herd_id, offset, page_count = 10) {
11
+ export async function server_get_more_zones_and_actions_for_herd(herd_id, offset, page_count = 10, client) {
12
12
  const from = offset * page_count;
13
13
  const to = from + page_count - 1;
14
- const supabase = await newServerClient();
14
+ const supabase = client || (await newServerClient());
15
15
  // make rpc call to get_events_with_tags_for_herd(herd_id, offset, limit)
16
16
  const { data, error } = await supabase.rpc("get_zones_and_actions_for_herd", {
17
17
  herd_id_caller: herd_id,
@@ -0,0 +1,8 @@
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ import type { IDevice } from "../types/db";
3
+ import type { IHerdModule } from "../types/herd_module";
4
+ export declare function primeDeviceDisplayNameCache(devices: Iterable<IDevice>): void;
5
+ export declare function useDeviceDisplayName(deviceId: number | null | undefined, options?: {
6
+ herdModules?: IHerdModule[];
7
+ supabase?: SupabaseClient;
8
+ }): string | null;