Skip to content

Commit 54c9c8f

Browse files
Commited migrations
1 parent 98d3e04 commit 54c9c8f

File tree

5 files changed

+787
-60
lines changed

5 files changed

+787
-60
lines changed

packages/api/generated-schema-clean.gql

+6
Original file line numberDiff line numberDiff line change
@@ -13060,6 +13060,7 @@ type Sketch implements Node {
1306013060
"""
1306113061
copyOf: Int
1306213062
createdAt: Datetime!
13063+
filterMvtUrl: String
1306313064

1306413065
"""
1306513066
Parent folder. Both regular sketches and collections may be nested within folders for organization purposes.
@@ -13162,6 +13163,8 @@ type SketchClass implements Node {
1316213163
sketch classes can only be digitized by admins.
1316313164
"""
1316413165
canDigitize: Boolean
13166+
filterApiServerLocation: String
13167+
filterApiVersion: Int!
1316513168

1316613169
"""Reads a single `Form` that is related to this `SketchClass`."""
1316713170
form: Form
@@ -13304,6 +13307,8 @@ input SketchClassPatch {
1330413307
1330513308
"""
1330613309
allowMulti: Boolean
13310+
filterApiServerLocation: String
13311+
filterApiVersion: Int
1330713312

1330813313
"""
1330913314
Geometry type users digitize. COLLECTION types act as a feature collection and have no drawn geometry.
@@ -13439,6 +13444,7 @@ enum SketchFoldersOrderBy {
1343913444
enum SketchGeometryType {
1344013445
CHOOSE_FEATURE
1344113446
COLLECTION
13447+
FILTERED_PLANNING_UNITS
1344213448
LINESTRING
1344313449
POINT
1344413450
POLYGON
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
--! Previous: sha1:1683d85c546802a54a712150f5991ca9f6b7655a
2+
--! Hash: sha1:9bc19cbc712fb81e17c7a84ccee62a3facdf5ea5
3+
4+
-- Enter migration here
5+
alter table sketch_classes add column if not exists filter_api_version int not null default 1;
6+
alter table sketch_classes add column if not exists filter_api_server_location text;
7+
8+
alter type sketch_geometry_type add value if not exists 'FILTERED_PLANNING_UNITS';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
--! Previous: sha1:9bc19cbc712fb81e17c7a84ccee62a3facdf5ea5
2+
--! Hash: sha1:a98e9d208313bdb12a74b6a18f984d1d203092e1
3+
4+
-- Enter migration here
5+
set role seasketch_superuser;
6+
delete from sketch_classes where project_id = (select id from projects where slug = 'superuser') and name = 'Filtered Planning Units';
7+
8+
insert into sketch_classes(
9+
project_id,
10+
name,
11+
geometry_type,
12+
mapbox_gl_style,
13+
is_template,
14+
template_description
15+
) values (
16+
(select id from projects where slug = 'superuser'),
17+
'Filtered Planning Units',
18+
'FILTERED_PLANNING_UNITS'::sketch_geometry_type,
19+
'{}'::jsonb,
20+
true,
21+
'Filter polygons by criteria. Requires an API server.'
22+
) on conflict do nothing;
23+
24+
select initialize_sketch_class_form_from_template((select id from sketch_classes where name = 'Filtered Planning Units' and is_template = true), (select id from forms where is_template = true and template_type = 'SKETCHES' and template_name = 'Basic Template'));
25+
set role postgres;
26+
27+
GRANT update (filter_api_server_location) on sketch_classes to seasketch_user;
28+
GRANT update (filter_api_version) on sketch_classes to seasketch_user;
29+
30+
delete from form_element_types where component_name = 'FilterInput';
31+
insert into form_element_types (
32+
component_name,
33+
label,
34+
is_input,
35+
is_surveys_only
36+
) values (
37+
'FilterInput',
38+
'Filter Input',
39+
true,
40+
false
41+
);
42+
43+
CREATE OR REPLACE FUNCTION public.before_sketch_insert_or_update() RETURNS trigger
44+
LANGUAGE plpgsql SECURITY DEFINER
45+
AS $$
46+
declare
47+
class_geometry_type sketch_geometry_type;
48+
allow_multi boolean;
49+
incoming_geometry_type text;
50+
new_geometry_type text;
51+
parent_collection_id int;
52+
begin
53+
select
54+
geometry_type,
55+
sketch_classes.allow_multi
56+
into
57+
class_geometry_type,
58+
allow_multi,
59+
incoming_geometry_type
60+
from
61+
sketch_classes
62+
where
63+
id = NEW.sketch_class_id;
64+
if NEW.folder_id is not null and NEW.collection_id is not null then
65+
raise exception 'Parent cannot be to both folder and collection';
66+
end if;
67+
if class_geometry_type = 'COLLECTION' then
68+
-- Also check for parent collection of parent folder (recursively)
69+
if NEW.collection_id is not null then
70+
if (select get_parent_collection_id('sketch', NEW.collection_id)) is not null then
71+
raise exception 'Nested collections are not allowed';
72+
end if;
73+
elsif NEW.folder_id is not null then
74+
if (select get_parent_collection_id('sketch_folder', NEW.folder_id)) is not null then
75+
raise exception 'Nested collections are not allowed';
76+
end if;
77+
end if;
78+
-- geom must be present unless a collection
79+
if NEW.geom is not null or NEW.user_geom is not null then
80+
raise exception 'Collections should not have geometry';
81+
else
82+
-- no nested collections
83+
if NEW.collection_id is not null then
84+
raise exception 'Nested collections are not allowed';
85+
else
86+
return NEW;
87+
end if;
88+
end if;
89+
elsif class_geometry_type = 'FILTERED_PLANNING_UNITS' then
90+
-- Also check for parent collection of parent folder (recursively)
91+
if NEW.collection_id is not null then
92+
raise exception 'Filtered planning units cannot be part of a collection';
93+
elsif NEW.folder_id is not null then
94+
if (select get_parent_collection_id('sketch_folder', NEW.folder_id)) is not null then
95+
raise exception 'Filtered planning units cannot be part of a collection';
96+
end if;
97+
end if;
98+
-- geom must be present unless a collection
99+
if NEW.geom is not null or NEW.user_geom is not null then
100+
raise exception 'Filtered planning units should not have geometry';
101+
else
102+
-- no nested collections
103+
if NEW.collection_id is not null then
104+
raise exception 'Filtered planning units cannot be part of a collection';
105+
else
106+
return NEW;
107+
end if;
108+
end if;
109+
else
110+
select geometrytype(NEW.user_geom) into new_geometry_type;
111+
-- geometry type must match sketch_class.geometry_type and sketch_class.allow_multi
112+
if (new_geometry_type = class_geometry_type::text) or (allow_multi and new_geometry_type like '%' || class_geometry_type::text) then
113+
-- if specifying a collection_id, must be in it's valid_children
114+
if NEW.collection_id is null or not exists(select 1 from sketch_classes_valid_children where parent_id in (select sketch_class_id from sketches where id = NEW.collection_id)) or exists(select 1 from sketch_classes_valid_children where parent_id in (select sketch_class_id from sketches where id = NEW.collection_id) and child_id = NEW.sketch_class_id) then
115+
return NEW;
116+
else
117+
raise exception 'Sketch is not a valid child of collection';
118+
end if;
119+
else
120+
raise exception 'Geometry type does not match sketch class';
121+
end if;
122+
end if;
123+
end;
124+
$$;
125+
126+
127+
CREATE OR REPLACE FUNCTION url_encode(input text)
128+
RETURNS text AS $$
129+
DECLARE
130+
cleaned_input text;
131+
output text := '';
132+
ch text;
133+
ch_code int;
134+
BEGIN
135+
-- Remove all extraneous whitespace from the input JSON text
136+
cleaned_input := regexp_replace(input, '\s+', '', 'g');
137+
138+
-- Perform URL encoding on the cleaned input
139+
FOR i IN 1..length(cleaned_input) LOOP
140+
ch := substr(cleaned_input, i, 1);
141+
ch_code := ascii(ch);
142+
-- Allow only URL-safe characters (alphanumeric and unreserved characters)
143+
IF ch ~ '[a-zA-Z0-9_.~-]' THEN
144+
output := output || ch;
145+
ELSE
146+
-- Use lpad and upper to ensure two-character hexadecimal representation
147+
output := output || '%' || lpad(upper(to_hex(ch_code)), 2, '0');
148+
END IF;
149+
END LOOP;
150+
151+
RETURN output;
152+
END;
153+
$$ LANGUAGE plpgsql;
154+
155+
grant execute on function url_encode to anon;
156+
comment on function url_encode is '@omit';
157+
158+
drop function if exists filter_state_to_search_string(jsonb, int);
159+
drop function if exists filter_state_to_search_string(jsonb);
160+
161+
CREATE OR REPLACE FUNCTION filter_state_to_search_string(filters jsonb, sketch_class_id int)
162+
RETURNS text AS $$
163+
DECLARE
164+
state jsonb := '{}';
165+
filter jsonb;
166+
attr text;
167+
result text;
168+
attribute_name text;
169+
filter_server_location text;
170+
filter_version int;
171+
final_url text;
172+
BEGIN
173+
-- Retrieve the filter_api_server_location and filter_api_version from sketch_classes table
174+
SELECT sketch_classes.filter_api_server_location, sketch_classes.filter_api_version
175+
INTO filter_server_location, filter_version
176+
FROM sketch_classes
177+
WHERE id = sketch_class_id;
178+
179+
-- If filter_api_server_location is NULL, return NULL
180+
IF filter_server_location IS NULL THEN
181+
RETURN NULL;
182+
END IF;
183+
184+
-- Loop through each attribute in the input JSONB object
185+
FOR attr, filter IN
186+
SELECT key, value FROM jsonb_each(filters)
187+
LOOP
188+
-- Only process if "selected" is true
189+
IF filter->>'selected' = 'true' THEN
190+
if filter->>'attribute' is not null then
191+
attribute_name := filter->>'attribute';
192+
else
193+
-- Look up the attribute name from form_elements for the current attr ID
194+
SELECT component_settings->>'attribute'
195+
INTO attribute_name
196+
FROM form_elements
197+
WHERE id = attr::int; -- Cast attr to integer to match form_elements ID
198+
end if;
199+
200+
201+
-- Default to the original key if no match is found
202+
IF attribute_name IS NULL THEN
203+
attribute_name := attr;
204+
END IF;
205+
206+
-- Check for numberState
207+
IF filter ? 'numberState' THEN
208+
state := state || jsonb_build_object(attribute_name, jsonb_build_object(
209+
'min', filter->'numberState'->'min',
210+
'max', filter->'numberState'->'max'
211+
));
212+
213+
-- Check for stringState
214+
ELSIF filter ? 'stringState' THEN
215+
state := state || jsonb_build_object(attribute_name, jsonb_build_object(
216+
'choices', filter->'stringState'
217+
));
218+
219+
-- Check for booleanState
220+
ELSIF filter ? 'booleanState' THEN
221+
state := state || jsonb_build_object(attribute_name, jsonb_build_object(
222+
'bool', COALESCE(filter->'booleanState', 'false')::boolean
223+
));
224+
END IF;
225+
END IF;
226+
END LOOP;
227+
228+
-- If no keys were added, return an empty string; otherwise, encode the JSON
229+
IF state = '{}'::jsonb THEN
230+
RETURN filter_server_location || '/v' || filter_version || '/mvt/{z}/{x}/{y}.pbf';
231+
ELSE
232+
-- Convert the JSONB object to a text string without formatting
233+
result := state::text;
234+
235+
-- URL encode the resulting JSON text using the updated url_encode function
236+
result := url_encode(result);
237+
238+
-- Construct the final URL
239+
final_url := filter_server_location || '/v' || filter_version || '/mvt/{z}/{x}/{y}.pbf?filter=' || result;
240+
RETURN final_url;
241+
END IF;
242+
END;
243+
$$ LANGUAGE plpgsql;
244+
245+
grant execute on function filter_state_to_search_string to anon;
246+
comment on function filter_state_to_search_string is '@omit';
247+
248+
alter table sketches drop column if exists filter_mvt_url;
249+
create or replace function sketches_filter_mvt_url(s sketches)
250+
returns text
251+
language sql
252+
security definer
253+
stable
254+
as $$
255+
select filter_state_to_search_string(s.properties, s.sketch_class_id);
256+
$$;
257+
258+
259+
grant execute on function sketches_filter_mvt_url to anon;
260+
-- alter table sketches add column if not exists filter_mvt_url text generated always as (filter_state_to_search_string(properties, sketch_class_id)) stored;
261+
262+
-- grant select(filter_mvt_url) on sketches to anon;
263+
264+
delete from form_element_types where component_name = 'CollapsibleGroup';
265+
insert into form_element_types (
266+
component_name,
267+
label,
268+
is_input,
269+
is_surveys_only
270+
) values (
271+
'CollapsibleGroup',
272+
'Collapsible Group',
273+
false,
274+
false
275+
);

packages/api/migrations/current.sql

-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1 @@
11
-- Enter migration here
2-
alter table sketch_classes add column if not exists filter_api_version int not null default 1;
3-
alter table sketch_classes add column if not exists filter_api_server_location text;
4-
5-
alter type sketch_geometry_type add value if not exists 'FILTERED_PLANNING_UNITS';
6-

0 commit comments

Comments
 (0)