@adventurelabs/scout-core 1.0.0
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.
- package/README.md +90 -0
- package/dist/api_keys/actions.d.ts +2 -0
- package/dist/api_keys/actions.js +20 -0
- package/dist/api_keys/index.d.ts +1 -0
- package/dist/api_keys/index.js +1 -0
- package/dist/constants/annotator.d.ts +1 -0
- package/dist/constants/annotator.js +11 -0
- package/dist/constants/app.d.ts +2 -0
- package/dist/constants/app.js +2 -0
- package/dist/constants/index.d.ts +2 -0
- package/dist/constants/index.js +2 -0
- package/dist/helpers/auth.d.ts +1 -0
- package/dist/helpers/auth.js +8 -0
- package/dist/helpers/bounding_boxes.d.ts +5 -0
- package/dist/helpers/bounding_boxes.js +43 -0
- package/dist/helpers/db.d.ts +3 -0
- package/dist/helpers/db.js +22 -0
- package/dist/helpers/devices.d.ts +8 -0
- package/dist/helpers/devices.js +92 -0
- package/dist/helpers/email.d.ts +7 -0
- package/dist/helpers/email.js +22 -0
- package/dist/helpers/events.d.ts +6 -0
- package/dist/helpers/events.js +71 -0
- package/dist/helpers/finance.d.ts +9 -0
- package/dist/helpers/finance.js +16 -0
- package/dist/helpers/gps.d.ts +5 -0
- package/dist/helpers/gps.js +11 -0
- package/dist/helpers/herds.d.ts +9 -0
- package/dist/helpers/herds.js +80 -0
- package/dist/helpers/index.d.ts +17 -0
- package/dist/helpers/index.js +17 -0
- package/dist/helpers/location.d.ts +1 -0
- package/dist/helpers/location.js +3 -0
- package/dist/helpers/plans.d.ts +5 -0
- package/dist/helpers/plans.js +61 -0
- package/dist/helpers/tags.d.ts +7 -0
- package/dist/helpers/tags.js +158 -0
- package/dist/helpers/time.d.ts +3 -0
- package/dist/helpers/time.js +11 -0
- package/dist/helpers/ui.d.ts +4 -0
- package/dist/helpers/ui.js +12 -0
- package/dist/helpers/users.d.ts +6 -0
- package/dist/helpers/users.js +66 -0
- package/dist/helpers/web.d.ts +1 -0
- package/dist/helpers/web.js +7 -0
- package/dist/helpers/zones.d.ts +18 -0
- package/dist/helpers/zones.js +108 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/useScoutDbListener.d.ts +1 -0
- package/dist/hooks/useScoutDbListener.js +94 -0
- package/dist/hooks/useScoutRefresh.d.ts +7 -0
- package/dist/hooks/useScoutRefresh.js +45 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +43 -0
- package/dist/providers/ScoutRefreshProvider.d.ts +1 -0
- package/dist/providers/ScoutRefreshProvider.js +8 -0
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.js +1 -0
- package/dist/store/hooks.d.ts +1 -0
- package/dist/store/hooks.js +3 -0
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.js +1 -0
- package/dist/store/scout.d.ts +103 -0
- package/dist/store/scout.js +196 -0
- package/dist/supabase/client.d.ts +1 -0
- package/dist/supabase/client.js +5 -0
- package/dist/supabase/index.d.ts +3 -0
- package/dist/supabase/index.js +3 -0
- package/dist/supabase/middleware.d.ts +2 -0
- package/dist/supabase/middleware.js +46 -0
- package/dist/supabase/server.d.ts +636 -0
- package/dist/supabase/server.js +22 -0
- package/dist/types/bounding_boxes.d.ts +27 -0
- package/dist/types/bounding_boxes.js +11 -0
- package/dist/types/db.d.ts +42 -0
- package/dist/types/db.js +15 -0
- package/dist/types/gps.d.ts +21 -0
- package/dist/types/gps.js +13 -0
- package/dist/types/herd_module.d.ts +31 -0
- package/dist/types/herd_module.js +82 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.js +6 -0
- package/dist/types/requests.d.ts +18 -0
- package/dist/types/requests.js +25 -0
- package/dist/types/supabase.d.ts +754 -0
- package/dist/types/supabase.js +25 -0
- package/dist/types/ui.d.ts +7 -0
- package/dist/types/ui.js +8 -0
- package/package.json +52 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
import { newServerClient } from "../supabase/server";
|
|
3
|
+
import { EnumWebResponse, IWebResponse, } from "../types/requests";
|
|
4
|
+
// function that fetches the plans our db given a herd id
|
|
5
|
+
export async function server_get_plans_by_herd(herd_id) {
|
|
6
|
+
const supabase = await newServerClient();
|
|
7
|
+
const { data, error } = await supabase
|
|
8
|
+
.from("plans")
|
|
9
|
+
.select("*")
|
|
10
|
+
.eq("herd_id", herd_id);
|
|
11
|
+
if (error) {
|
|
12
|
+
return {
|
|
13
|
+
status: EnumWebResponse.ERROR,
|
|
14
|
+
msg: error.message,
|
|
15
|
+
data: null,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
return IWebResponse.success(data).to_compatible();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// function that uploads plan to our db
|
|
23
|
+
export async function server_create_plans(plans) {
|
|
24
|
+
// loop through plans and format
|
|
25
|
+
let formatted_plans = plans.map((plan) => {
|
|
26
|
+
let formatted_plan = { ...plan };
|
|
27
|
+
delete formatted_plan.id;
|
|
28
|
+
delete formatted_plan.inserted_at;
|
|
29
|
+
return formatted_plan;
|
|
30
|
+
});
|
|
31
|
+
const supabase = await newServerClient();
|
|
32
|
+
// insert data and return the response
|
|
33
|
+
const { data, error } = await supabase
|
|
34
|
+
.from("plans")
|
|
35
|
+
.insert(formatted_plans)
|
|
36
|
+
.select("*");
|
|
37
|
+
if (error) {
|
|
38
|
+
return {
|
|
39
|
+
status: EnumWebResponse.ERROR,
|
|
40
|
+
msg: error.message,
|
|
41
|
+
data: null,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
return IWebResponse.success(data).to_compatible();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function server_delete_plans_by_ids(plan_ids) {
|
|
49
|
+
const supabase = await newServerClient();
|
|
50
|
+
const { error } = await supabase.from("plans").delete().in("id", plan_ids);
|
|
51
|
+
if (error) {
|
|
52
|
+
return {
|
|
53
|
+
status: EnumWebResponse.ERROR,
|
|
54
|
+
msg: error.message,
|
|
55
|
+
data: false,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
return IWebResponse.success(true).to_compatible();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { IEventWithTags, ITag } from "../types/db";
|
|
2
|
+
import { IWebResponseCompatible } from "../types/requests";
|
|
3
|
+
export declare function server_create_tags(tags: ITag[]): Promise<IWebResponseCompatible<ITag[]>>;
|
|
4
|
+
export declare function server_delete_tags_by_ids(tag_ids: number[]): Promise<IWebResponseCompatible<boolean>>;
|
|
5
|
+
export declare function server_get_more_events_with_tags_by_herd(herd_id: number, offset: number, page_count?: number): Promise<IWebResponseCompatible<IEventWithTags[]>>;
|
|
6
|
+
export declare function server_get_events_and_tags_for_device(device_id: number, limit?: number): Promise<IWebResponseCompatible<IEventWithTags[]>>;
|
|
7
|
+
export declare function get_event_and_tags_by_event_id(event_id: number): Promise<IWebResponseCompatible<IEventWithTags>>;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
// add tag to db
|
|
3
|
+
import { newServerClient } from "../supabase/server";
|
|
4
|
+
import { EnumWebResponse, IWebResponse, } from "../types/requests";
|
|
5
|
+
export async function server_create_tags(tags) {
|
|
6
|
+
const supabase = await newServerClient();
|
|
7
|
+
// remove id key from tags
|
|
8
|
+
const formatted_tags = tags.map((tag) => {
|
|
9
|
+
const { id, ...rest } = tag;
|
|
10
|
+
return {
|
|
11
|
+
...rest,
|
|
12
|
+
observation_type: rest.observation_type,
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
const { data, error } = await supabase
|
|
16
|
+
.from("tags")
|
|
17
|
+
.insert(formatted_tags)
|
|
18
|
+
.select("*");
|
|
19
|
+
if (error) {
|
|
20
|
+
return {
|
|
21
|
+
status: EnumWebResponse.ERROR,
|
|
22
|
+
msg: error.message,
|
|
23
|
+
data: null,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
return IWebResponse.success(data).to_compatible();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export async function server_delete_tags_by_ids(tag_ids) {
|
|
31
|
+
const supabase = await newServerClient();
|
|
32
|
+
const { error } = await supabase.from("tags").delete().in("id", tag_ids);
|
|
33
|
+
if (error) {
|
|
34
|
+
return {
|
|
35
|
+
status: EnumWebResponse.ERROR,
|
|
36
|
+
msg: error.message,
|
|
37
|
+
data: false,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
return IWebResponse.success(true).to_compatible();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// export async function server_get_events_with_tags_by_herd(
|
|
45
|
+
// herd_id: number
|
|
46
|
+
// ): Promise<IWebResponseCompatible<IEventWithTags[]>> {
|
|
47
|
+
// const supabase = await newServerClient();
|
|
48
|
+
// const { data, error } = await supabase
|
|
49
|
+
// .from("events")
|
|
50
|
+
// .select(
|
|
51
|
+
// `
|
|
52
|
+
// *,
|
|
53
|
+
// tags: tags (*)
|
|
54
|
+
// `
|
|
55
|
+
// )
|
|
56
|
+
// .eq("devices.herd_id", herd_id)
|
|
57
|
+
// .order("timestamp_observation", { ascending: false });
|
|
58
|
+
// if (error) {
|
|
59
|
+
// return {
|
|
60
|
+
// status: EnumWebResponse.ERROR,
|
|
61
|
+
// msg: error.message,
|
|
62
|
+
// data: [],
|
|
63
|
+
// };
|
|
64
|
+
// }
|
|
65
|
+
// return IWebResponse.success(data).to_compatible();
|
|
66
|
+
// }
|
|
67
|
+
export async function server_get_more_events_with_tags_by_herd(herd_id, offset, page_count = 10) {
|
|
68
|
+
const from = offset * page_count;
|
|
69
|
+
const to = from + page_count - 1;
|
|
70
|
+
const supabase = await newServerClient();
|
|
71
|
+
// make rpc call to get_events_with_tags_for_herd(herd_id, offset, limit)
|
|
72
|
+
const { data, error } = await supabase.rpc("get_events_and_tags_for_herd", {
|
|
73
|
+
herd_id_caller: herd_id,
|
|
74
|
+
offset_caller: from,
|
|
75
|
+
limit_caller: page_count,
|
|
76
|
+
});
|
|
77
|
+
if (error) {
|
|
78
|
+
console.warn("Error fetching events with tags by herd:", error.message);
|
|
79
|
+
return {
|
|
80
|
+
status: EnumWebResponse.ERROR,
|
|
81
|
+
msg: error.message,
|
|
82
|
+
data: [],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// iterate through data if tags contains null, remove it
|
|
86
|
+
const filtered_data = data.map((event) => {
|
|
87
|
+
if (!event.tags)
|
|
88
|
+
return event;
|
|
89
|
+
event.tags = event.tags.filter((tag) => tag !== null);
|
|
90
|
+
return event;
|
|
91
|
+
});
|
|
92
|
+
return IWebResponse.success(filtered_data).to_compatible();
|
|
93
|
+
}
|
|
94
|
+
export async function server_get_events_and_tags_for_device(device_id, limit = 3) {
|
|
95
|
+
const supabase = await newServerClient();
|
|
96
|
+
// make rpc call to get_events_with_tags_for_device(device_id, limit)
|
|
97
|
+
const { data, error } = await supabase.rpc("get_events_and_tags_for_device", {
|
|
98
|
+
device_id_caller: device_id,
|
|
99
|
+
limit_caller: limit,
|
|
100
|
+
});
|
|
101
|
+
if (error) {
|
|
102
|
+
console.warn("Error fetching recent events with tags by device:", error.message);
|
|
103
|
+
return {
|
|
104
|
+
status: EnumWebResponse.ERROR,
|
|
105
|
+
msg: error.message,
|
|
106
|
+
data: [],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return IWebResponse.success(data).to_compatible();
|
|
110
|
+
}
|
|
111
|
+
export async function get_event_and_tags_by_event_id(event_id) {
|
|
112
|
+
const supabase = await newServerClient();
|
|
113
|
+
// use actual sql query to get event and tags instead of rpc
|
|
114
|
+
const { data, error } = await supabase
|
|
115
|
+
.from("events")
|
|
116
|
+
.select(`
|
|
117
|
+
*,
|
|
118
|
+
tags: tags (*)
|
|
119
|
+
`)
|
|
120
|
+
.eq("id", event_id);
|
|
121
|
+
if (error) {
|
|
122
|
+
console.warn("Error fetching event with tags by event id:", error.message);
|
|
123
|
+
return {
|
|
124
|
+
status: EnumWebResponse.ERROR,
|
|
125
|
+
msg: error.message,
|
|
126
|
+
data: null,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (!data[0]) {
|
|
130
|
+
return {
|
|
131
|
+
status: EnumWebResponse.ERROR,
|
|
132
|
+
msg: "Event not found",
|
|
133
|
+
data: null,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// Transform location to latitude/longitude
|
|
137
|
+
const transformedData = {
|
|
138
|
+
id: data[0].id,
|
|
139
|
+
inserted_at: data[0].inserted_at,
|
|
140
|
+
message: data[0].message,
|
|
141
|
+
media_url: data[0].media_url,
|
|
142
|
+
latitude: data[0].location
|
|
143
|
+
? data[0].location.coordinates[1]
|
|
144
|
+
: null,
|
|
145
|
+
longitude: data[0].location
|
|
146
|
+
? data[0].location.coordinates[0]
|
|
147
|
+
: null,
|
|
148
|
+
altitude: data[0].altitude,
|
|
149
|
+
heading: data[0].heading,
|
|
150
|
+
media_type: data[0].media_type,
|
|
151
|
+
device_id: data[0].device_id,
|
|
152
|
+
timestamp_observation: data[0].timestamp_observation,
|
|
153
|
+
is_public: data[0].is_public,
|
|
154
|
+
tags: data[0].tags || [],
|
|
155
|
+
earthranger_url: data[0].earthranger_url,
|
|
156
|
+
};
|
|
157
|
+
return IWebResponse.success(transformedData).to_compatible();
|
|
158
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function convertSecondsSinceEpochToDate(secondsSinceEpoch) {
|
|
2
|
+
return new Date(secondsSinceEpoch * 1000);
|
|
3
|
+
}
|
|
4
|
+
// convert iso ISO 8601 string to date
|
|
5
|
+
export function convertIsoStringToDate(isoString) {
|
|
6
|
+
return new Date(isoString);
|
|
7
|
+
}
|
|
8
|
+
// convert date to time string of format "HH:MM:SS, DD/MM/YYYY"
|
|
9
|
+
export function convertDateToTimeString(date) {
|
|
10
|
+
return `${date.toLocaleTimeString()}, ${date.toLocaleDateString()} UTC`;
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function formatRoleCasing(role) {
|
|
2
|
+
return role.charAt(0).toUpperCase() + role.slice(1);
|
|
3
|
+
}
|
|
4
|
+
export function formatUsernameInitials(username) {
|
|
5
|
+
if (!username) {
|
|
6
|
+
return "";
|
|
7
|
+
}
|
|
8
|
+
return username.slice(0, 2).toUpperCase();
|
|
9
|
+
}
|
|
10
|
+
export function roundToXDecimals(num, decimals) {
|
|
11
|
+
return Math.round(num * Math.pow(10, decimals)) / Math.pow(10, decimals);
|
|
12
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { IUser, IUserAndRole, IUserRolePerHerd, Role } from "../types/db";
|
|
2
|
+
import { IWebResponseCompatible } from "../types/requests";
|
|
3
|
+
export declare function server_get_user_roles(herd_id: number): Promise<IWebResponseCompatible<IUserRolePerHerd[]>>;
|
|
4
|
+
export declare function server_get_user(): Promise<IWebResponseCompatible<IUser | null>>;
|
|
5
|
+
export declare function server_get_users_with_herd_access(herd_id: number): Promise<IWebResponseCompatible<IUserAndRole[] | null>>;
|
|
6
|
+
export declare function server_upsert_user_with_role(herd_id: number, username: string, role: Role): Promise<IWebResponseCompatible<IUserAndRole | null>>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
import { newServerClient } from "../supabase/server";
|
|
3
|
+
import { IWebResponse, EnumWebResponse, } from "../types/requests";
|
|
4
|
+
export async function server_get_user_roles(herd_id) {
|
|
5
|
+
const supabase = await newServerClient();
|
|
6
|
+
// fetch user role for herd
|
|
7
|
+
const { data, error } = await supabase
|
|
8
|
+
.from("users_roles_per_herd")
|
|
9
|
+
.select("*")
|
|
10
|
+
.eq("herd_id", herd_id);
|
|
11
|
+
if (!data) {
|
|
12
|
+
return {
|
|
13
|
+
status: EnumWebResponse.ERROR,
|
|
14
|
+
msg: `No user role found for herd ${herd_id}`,
|
|
15
|
+
data: null,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
// TODO: DETERMINE WHEN TO PASS ERROR
|
|
19
|
+
let response = IWebResponse.success(data);
|
|
20
|
+
return response.to_compatible();
|
|
21
|
+
}
|
|
22
|
+
export async function server_get_user() {
|
|
23
|
+
const supabase = await newServerClient();
|
|
24
|
+
const { data } = await supabase.auth.getUser();
|
|
25
|
+
const new_user = data.user;
|
|
26
|
+
return IWebResponse.success(new_user).to_compatible();
|
|
27
|
+
}
|
|
28
|
+
export async function server_get_users_with_herd_access(herd_id) {
|
|
29
|
+
const supabase = await newServerClient();
|
|
30
|
+
const { data, error } = await supabase
|
|
31
|
+
.from("users_roles_per_herd")
|
|
32
|
+
.select("user:users(*), role")
|
|
33
|
+
.eq("herd_id", herd_id);
|
|
34
|
+
if (error) {
|
|
35
|
+
return IWebResponse.error(error.message).to_compatible();
|
|
36
|
+
}
|
|
37
|
+
return IWebResponse.success(data).to_compatible();
|
|
38
|
+
}
|
|
39
|
+
export async function server_upsert_user_with_role(herd_id, username, role) {
|
|
40
|
+
const supabase = await newServerClient();
|
|
41
|
+
// first try to get user by username
|
|
42
|
+
const { data: user, error: user_error } = await supabase
|
|
43
|
+
.from("users")
|
|
44
|
+
.select("*")
|
|
45
|
+
.eq("username", username)
|
|
46
|
+
.single();
|
|
47
|
+
if (user_error) {
|
|
48
|
+
return IWebResponse.error("Unable to fetch user with provided username").to_compatible();
|
|
49
|
+
}
|
|
50
|
+
if (!user) {
|
|
51
|
+
return IWebResponse.error("User not found").to_compatible();
|
|
52
|
+
}
|
|
53
|
+
const newRoleForDb = {
|
|
54
|
+
user_id: user.id,
|
|
55
|
+
herd_id: herd_id,
|
|
56
|
+
role: role,
|
|
57
|
+
};
|
|
58
|
+
// upddate or insert user role
|
|
59
|
+
const { data, error } = await supabase
|
|
60
|
+
.from("users_roles_per_herd")
|
|
61
|
+
.upsert(newRoleForDb);
|
|
62
|
+
if (error) {
|
|
63
|
+
return IWebResponse.error("Unable to update or add user with provided username. Ensure you have sufficient permissions.").to_compatible();
|
|
64
|
+
}
|
|
65
|
+
return IWebResponse.success(data).to_compatible();
|
|
66
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getOrigin(): string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function getOrigin() {
|
|
2
|
+
let url = process?.env?.NEXT_PUBLIC_SITE_URL ?? // Set this to your site URL in production env.
|
|
3
|
+
"http://localhost:3000";
|
|
4
|
+
// Make sure to include `https://` when not localhost.
|
|
5
|
+
url = url.startsWith("http") ? url : `https://${url}`;
|
|
6
|
+
return url;
|
|
7
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { IZoneWithActions } from "../types/db";
|
|
2
|
+
import { IWebResponseCompatible } from "../types/requests";
|
|
3
|
+
/**
|
|
4
|
+
* Get more zones and actions for a herd
|
|
5
|
+
* @param herd_id - The ID of the herd to get zones and actions for
|
|
6
|
+
* @param offset - The offset to start the query from
|
|
7
|
+
* @param page_count - The number of zones and actions to return
|
|
8
|
+
* @returns A list of zones and actions
|
|
9
|
+
* @throws An error if the zones or actions are not fetched
|
|
10
|
+
*/
|
|
11
|
+
export declare function server_get_more_zones_and_actions_for_herd(herd_id: number, offset: number, page_count?: number): Promise<IWebResponseCompatible<IZoneWithActions[]>>;
|
|
12
|
+
/**
|
|
13
|
+
* Create zones and actions for a herd
|
|
14
|
+
* @param zones - The zones to create
|
|
15
|
+
* @returns A list of zones and actions
|
|
16
|
+
* @throws An error if the zones or actions are not created
|
|
17
|
+
*/
|
|
18
|
+
export declare function server_create_zones_with_actions(zones: IZoneWithActions[]): Promise<IWebResponseCompatible<IZoneWithActions[]>>;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { newServerClient } from "../supabase/server";
|
|
2
|
+
import { EnumWebResponse, IWebResponse, } from "../types/requests";
|
|
3
|
+
/**
|
|
4
|
+
* Get more zones and actions for a herd
|
|
5
|
+
* @param herd_id - The ID of the herd to get zones and actions for
|
|
6
|
+
* @param offset - The offset to start the query from
|
|
7
|
+
* @param page_count - The number of zones and actions to return
|
|
8
|
+
* @returns A list of zones and actions
|
|
9
|
+
* @throws An error if the zones or actions are not fetched
|
|
10
|
+
*/
|
|
11
|
+
export async function server_get_more_zones_and_actions_for_herd(herd_id, offset, page_count = 10) {
|
|
12
|
+
const from = offset * page_count;
|
|
13
|
+
const to = from + page_count - 1;
|
|
14
|
+
const supabase = await newServerClient();
|
|
15
|
+
// make rpc call to get_events_with_tags_for_herd(herd_id, offset, limit)
|
|
16
|
+
const { data, error } = await supabase.rpc("get_zones_and_actions_for_herd", {
|
|
17
|
+
herd_id_caller: herd_id,
|
|
18
|
+
offset_caller: from,
|
|
19
|
+
limit_caller: page_count,
|
|
20
|
+
});
|
|
21
|
+
if (error) {
|
|
22
|
+
console.warn("Error fetching zones and actions for herd:", error.message);
|
|
23
|
+
return {
|
|
24
|
+
status: EnumWebResponse.ERROR,
|
|
25
|
+
msg: error.message,
|
|
26
|
+
data: [],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return IWebResponse.success(data.map((zone) => ({
|
|
30
|
+
...zone,
|
|
31
|
+
actions: zone.actions ?? [],
|
|
32
|
+
}))).to_compatible();
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Create zones and actions for a herd
|
|
36
|
+
* @param zones - The zones to create
|
|
37
|
+
* @returns A list of zones and actions
|
|
38
|
+
* @throws An error if the zones or actions are not created
|
|
39
|
+
*/
|
|
40
|
+
export async function server_create_zones_with_actions(zones) {
|
|
41
|
+
// loop through plans and format
|
|
42
|
+
let actions = zones.flatMap((zone) => zone.actions);
|
|
43
|
+
let formatted_zones = zones.map((zone) => {
|
|
44
|
+
let formatted_zone = { ...zone };
|
|
45
|
+
delete formatted_zone.id;
|
|
46
|
+
delete formatted_zone.inserted_at;
|
|
47
|
+
delete formatted_zone.actions;
|
|
48
|
+
return formatted_zone;
|
|
49
|
+
});
|
|
50
|
+
let formatted_actions = actions.map((action) => {
|
|
51
|
+
let formatted_action = { ...action };
|
|
52
|
+
delete formatted_action.id;
|
|
53
|
+
delete formatted_action.zone_id;
|
|
54
|
+
delete formatted_action.inserted_at;
|
|
55
|
+
return formatted_action;
|
|
56
|
+
});
|
|
57
|
+
const supabase = await newServerClient();
|
|
58
|
+
// insert data and return the response
|
|
59
|
+
const { data, error } = await supabase
|
|
60
|
+
.from("zones")
|
|
61
|
+
.insert(formatted_zones)
|
|
62
|
+
.select("*");
|
|
63
|
+
if (error) {
|
|
64
|
+
let msg = `Error creating zones: ${error.message}`;
|
|
65
|
+
return {
|
|
66
|
+
status: EnumWebResponse.ERROR,
|
|
67
|
+
msg: msg,
|
|
68
|
+
data: null,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// get zone id
|
|
72
|
+
let zone_ids = data.map((zone) => {
|
|
73
|
+
return zone.id;
|
|
74
|
+
});
|
|
75
|
+
// if zone ids length is zero, return error
|
|
76
|
+
if (zone_ids.length === 0) {
|
|
77
|
+
return {
|
|
78
|
+
status: EnumWebResponse.ERROR,
|
|
79
|
+
msg: "No zones created",
|
|
80
|
+
data: null,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// add zone id to formatted actions
|
|
84
|
+
formatted_actions.forEach((action) => {
|
|
85
|
+
action.zone_id = zone_ids[0];
|
|
86
|
+
});
|
|
87
|
+
// insert actions
|
|
88
|
+
const { data: data_actions, error: error_actions } = await supabase
|
|
89
|
+
.from("actions")
|
|
90
|
+
.insert(formatted_actions)
|
|
91
|
+
.select("*");
|
|
92
|
+
if (error_actions) {
|
|
93
|
+
let msg = `Error creating actions: ${error_actions.message}`;
|
|
94
|
+
return {
|
|
95
|
+
status: EnumWebResponse.ERROR,
|
|
96
|
+
msg: msg,
|
|
97
|
+
data: null,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
// merge data and data_actions
|
|
101
|
+
const merged_data = data.map((zone) => {
|
|
102
|
+
return {
|
|
103
|
+
...zone,
|
|
104
|
+
actions: data_actions.filter((action) => action.zone_id === zone.id),
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
return IWebResponse.success(merged_data).to_compatible();
|
|
108
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useScoutDbListener(): void;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useAppDispatch } from "../store/hooks";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { addDevice, addPlan, addTag, deleteDevice, deletePlan, deleteTag, updateDevice, updatePlan, updateTag, } from "../store/scout";
|
|
5
|
+
import { createBrowserClient } from "@supabase/ssr";
|
|
6
|
+
import { get_device_by_id } from "../helpers/devices";
|
|
7
|
+
import { EnumWebResponse } from "../types/requests";
|
|
8
|
+
export function useScoutDbListener() {
|
|
9
|
+
const url = process.env.NEXT_PUBLIC_SUPABASE_URL || "";
|
|
10
|
+
const anon_key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "";
|
|
11
|
+
const supabase = useRef(null);
|
|
12
|
+
const dispatch = useAppDispatch();
|
|
13
|
+
function handleTagInserts(payload) {
|
|
14
|
+
dispatch(addTag(payload.new));
|
|
15
|
+
}
|
|
16
|
+
function handleTagDeletes(payload) {
|
|
17
|
+
dispatch(deleteTag(payload.old));
|
|
18
|
+
}
|
|
19
|
+
function handleTagUpdates(payload) {
|
|
20
|
+
dispatch(updateTag(payload.new));
|
|
21
|
+
}
|
|
22
|
+
async function handleDeviceInserts(payload) {
|
|
23
|
+
const response_device = await get_device_by_id(payload.new.id);
|
|
24
|
+
if (response_device.status == EnumWebResponse.SUCCESS &&
|
|
25
|
+
response_device.data) {
|
|
26
|
+
dispatch(addDevice(response_device.data));
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
console.error("error getting device by id", payload);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function handleDeviceDeletes(payload) {
|
|
33
|
+
dispatch(deleteDevice(payload.old));
|
|
34
|
+
}
|
|
35
|
+
async function handleDeviceUpdates(payload) {
|
|
36
|
+
const response_device = await get_device_by_id(payload.new.id);
|
|
37
|
+
if (response_device.status == EnumWebResponse.SUCCESS &&
|
|
38
|
+
response_device.data) {
|
|
39
|
+
dispatch(updateDevice(response_device.data));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.error("error getting device by id", payload);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function handlePlanInserts(payload) {
|
|
46
|
+
dispatch(addPlan(payload.new));
|
|
47
|
+
}
|
|
48
|
+
function handlePlanDeletes(payload) {
|
|
49
|
+
dispatch(deletePlan(payload.old));
|
|
50
|
+
}
|
|
51
|
+
function handlePlanUpdates(payload) {
|
|
52
|
+
dispatch(updatePlan(payload.new));
|
|
53
|
+
}
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
const newSupabase = createBrowserClient(url, anon_key);
|
|
56
|
+
newSupabase
|
|
57
|
+
.channel("plans_insert")
|
|
58
|
+
.on("postgres_changes", { event: "INSERT", schema: "public", table: "plans" }, handlePlanInserts)
|
|
59
|
+
.subscribe();
|
|
60
|
+
newSupabase
|
|
61
|
+
.channel("plans_delete")
|
|
62
|
+
.on("postgres_changes", { event: "DELETE", schema: "public", table: "plans" }, handlePlanDeletes)
|
|
63
|
+
.subscribe();
|
|
64
|
+
newSupabase
|
|
65
|
+
.channel("plans_update")
|
|
66
|
+
.on("postgres_changes", { event: "UPDATE", schema: "public", table: "plans" }, handlePlanUpdates)
|
|
67
|
+
.subscribe();
|
|
68
|
+
newSupabase
|
|
69
|
+
.channel("devices_delete")
|
|
70
|
+
.on("postgres_changes", { event: "DELETE", schema: "public", table: "devices" }, handleDeviceDeletes)
|
|
71
|
+
.subscribe();
|
|
72
|
+
newSupabase
|
|
73
|
+
.channel("devices_insert")
|
|
74
|
+
.on("postgres_changes", { event: "INSERT", schema: "public", table: "devices" }, handleDeviceInserts)
|
|
75
|
+
.subscribe();
|
|
76
|
+
newSupabase
|
|
77
|
+
.channel("devices_update")
|
|
78
|
+
.on("postgres_changes", { event: "UPDATE", schema: "public", table: "devices" }, handleDeviceUpdates)
|
|
79
|
+
.subscribe();
|
|
80
|
+
newSupabase
|
|
81
|
+
.channel("tags_insert")
|
|
82
|
+
.on("postgres_changes", { event: "INSERT", schema: "public", table: "tags" }, handleTagInserts)
|
|
83
|
+
.subscribe();
|
|
84
|
+
newSupabase
|
|
85
|
+
.channel("tags_delete")
|
|
86
|
+
.on("postgres_changes", { event: "DELETE", schema: "public", table: "tags" }, handleTagDeletes)
|
|
87
|
+
.subscribe();
|
|
88
|
+
newSupabase
|
|
89
|
+
.channel("tags_update")
|
|
90
|
+
.on("postgres_changes", { event: "UPDATE", schema: "public", table: "tags" }, handleTagUpdates)
|
|
91
|
+
.subscribe();
|
|
92
|
+
supabase.current = newSupabase;
|
|
93
|
+
}, []);
|
|
94
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { useAppDispatch } from "../store/hooks";
|
|
3
|
+
import { EnumScoutStateStatus, setActiveHerdId, setHerdModules, setStatus, setUser, } from "../store/scout";
|
|
4
|
+
import { server_load_herd_modules } from "../helpers/herds";
|
|
5
|
+
import { server_get_user } from "../helpers/users";
|
|
6
|
+
export function useScoutRefresh(options = {}) {
|
|
7
|
+
const { autoRefresh = true, onRefreshComplete } = options;
|
|
8
|
+
const dispatch = useAppDispatch();
|
|
9
|
+
const handleRefresh = async () => {
|
|
10
|
+
dispatch(setStatus(EnumScoutStateStatus.LOADING));
|
|
11
|
+
try {
|
|
12
|
+
const compatible_new_herd_modules = await server_load_herd_modules();
|
|
13
|
+
const res_new_user = await server_get_user();
|
|
14
|
+
dispatch(setHerdModules(compatible_new_herd_modules));
|
|
15
|
+
dispatch(setUser(res_new_user.data));
|
|
16
|
+
// Check local storage for a last selected herd
|
|
17
|
+
if (localStorage.getItem("last_selected_herd")) {
|
|
18
|
+
const found_herd = compatible_new_herd_modules.find((hm) => hm.herd.id.toString() === localStorage.getItem("last_selected_herd"))?.herd;
|
|
19
|
+
// If herd is found then set it
|
|
20
|
+
if (found_herd) {
|
|
21
|
+
dispatch(setActiveHerdId(found_herd.id.toString()));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// If there is no last selected herd then select the first one
|
|
25
|
+
else if (compatible_new_herd_modules.length > 0) {
|
|
26
|
+
localStorage.setItem("last_selected_herd", compatible_new_herd_modules[0].herd.id.toString());
|
|
27
|
+
dispatch(setActiveHerdId(compatible_new_herd_modules[0].herd.id.toString()));
|
|
28
|
+
}
|
|
29
|
+
dispatch(setStatus(EnumScoutStateStatus.DONE_LOADING));
|
|
30
|
+
onRefreshComplete?.();
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
console.error("Error refreshing scout data:", error);
|
|
34
|
+
dispatch(setStatus(EnumScoutStateStatus.DONE_LOADING));
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (autoRefresh) {
|
|
39
|
+
handleRefresh();
|
|
40
|
+
}
|
|
41
|
+
}, [autoRefresh]);
|
|
42
|
+
return {
|
|
43
|
+
handleRefresh,
|
|
44
|
+
};
|
|
45
|
+
}
|